Skip to content

Commit

Permalink
Use attributes as annotation
Browse files Browse the repository at this point in the history
  • Loading branch information
jcouv committed May 6, 2018
1 parent 4bdc40d commit e66b11f
Show file tree
Hide file tree
Showing 7 changed files with 427 additions and 89 deletions.
68 changes: 55 additions & 13 deletions src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
Expand Down Expand Up @@ -1574,16 +1575,26 @@ public override BoundNode VisitCall(BoundCall node)
ReplayReadsAndWrites(localFunc, node.Syntax, writes: true);
}

if (MethodEnsuresTrueWhenExits(method) && arguments.Length > 0)
string key = ExtraAnnotations.MakeMethodKey(method);
ImmutableArray<AttributeDescription> extraAttributes = ExtraAnnotations.GetExtraAttributes(key);
if (MethodEnsuresTrueWhenExits(method, extraAttributes))
{
VisitTrueWhenExits(condition: arguments[0]);
var condition = TryGetFirstArgument(method, arguments);
if (condition != null)
{
VisitTrueWhenExits(condition);
}
}

if (MethodReturnsTrueWhenNotNull(method) && arguments.Length > 0)
if (MethodReturnsTrueWhenNotNull(method, extraAttributes))
{
VisitTrueWhenNotNull(arguments[0], method.ReturnType.TypeSymbol);
_result = method.ReturnType;
return null;
var condition = TryGetFirstArgument(method, arguments);
if (condition != null)
{
VisitTrueWhenNotNull(condition, method.ReturnType.TypeSymbol);
_result = method.ReturnType;
return null;
}
}

Debug.Assert(!IsConditionalState);
Expand All @@ -1595,14 +1606,24 @@ public override BoundNode VisitCall(BoundCall node)
return null;
}

private bool MethodReturnsTrueWhenNotNull(MethodSymbol method)
private BoundExpression TryGetFirstArgument(MethodSymbol method, ImmutableArray<BoundExpression> arguments)
{
if (method.IsExtensionMethod)
{
return (arguments.Length > 1) ? arguments[1] : null;
}

return (arguments.Length > 0) ? arguments[0] : null;
}

private bool MethodReturnsTrueWhenNotNull(MethodSymbol method, ImmutableArray<AttributeDescription> extraAttributes)
{
if (method.ReturnType.SpecialType != SpecialType.System_Boolean)
{
return false;
}

return method.Equals(compilation.GetWellKnownTypeMember(WellKnownMember.System_String_IsNullOrEmpty));
return MethodHasAttribute(method, extraAttributes, AttributeDescription.TrueWhenNotNullAttribute);
}

private void VisitTrueWhenNotNull(BoundExpression operand, TypeSymbol boolType)
Expand Down Expand Up @@ -1633,12 +1654,33 @@ internal static BoundBinaryOperator LogicalOr(BoundExpression left, BoundExpress
return new BoundBinaryOperator(left.Syntax, BinaryOperatorKind.LogicalBoolOr, left, right, constantValueOpt: null, methodOpt: null, LookupResultKind.Viable, left.Type);
}

private bool MethodEnsuresTrueWhenExits(MethodSymbol method)
private bool MethodEnsuresTrueWhenExits(MethodSymbol method, ImmutableArray<AttributeDescription> extraAttributes)
{
return MethodHasAttribute(method, extraAttributes, AttributeDescription.EnsuresTrueWhenExitsAttribute);
}

private bool MethodHasAttribute(MethodSymbol method, ImmutableArray<AttributeDescription> extraAttributes, AttributeDescription description)
{
return method.Equals(compilation.GetWellKnownTypeMember(WellKnownMember.System_Diagnostics_Debug_Assert1)) ||
method.Equals(compilation.GetWellKnownTypeMember(WellKnownMember.System_Diagnostics_Debug_Assert2)) ||
method.Equals(compilation.GetWellKnownTypeMember(WellKnownMember.System_Diagnostics_Debug_Assert3)) ||
method.Equals(compilation.GetWellKnownTypeMember(WellKnownMember.System_Diagnostics_Debug_Assert4));
foreach (var attribute in method.GetAttributes())
{
if (attribute.IsTargetAttribute(method, description))
{
return true;
}
}

if (!extraAttributes.IsDefault)
{
foreach (var attribute in extraAttributes)
{
if (attribute.Equals(description))
{
return true;
}
}
}

return false;
}

private void VisitTrueWhenExits(BoundExpression condition)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,17 @@ internal static class ExtraAnnotations
{ "System.String System.String.Concat(System.String, System.String)", Parameters(Nullable(false), Nullable(true), Nullable(true)) },
}.ToImmutableDictionary();

private static readonly ImmutableDictionary<string, ImmutableArray<AttributeDescription>> Attributes =
new Dictionary<string, ImmutableArray<AttributeDescription>>
{
{ "System.Void System.Diagnostics.Debug.Assert(System.Boolean)", Descriptions(AttributeDescription.EnsuresTrueWhenExitsAttribute) },
{ "System.Void System.Diagnostics.Debug.Assert(System.Boolean, System.String)", Descriptions(AttributeDescription.EnsuresTrueWhenExitsAttribute) },
{ "System.Void System.Diagnostics.Debug.Assert(System.Boolean, System.String, System.String)", Descriptions(AttributeDescription.EnsuresTrueWhenExitsAttribute) },
{ "System.Void System.Diagnostics.Debug.Assert(System.Boolean, System.String, System.String, System.Object[])", Descriptions(AttributeDescription.EnsuresTrueWhenExitsAttribute) },
{ "System.Boolean System.String.IsNullOrEmpty(System.String)", Descriptions(AttributeDescription.TrueWhenNotNullAttribute) },
{ "System.Boolean System.String.IsNullOrWhiteSpace(System.String)", Descriptions(AttributeDescription.TrueWhenNotNullAttribute) },
}.ToImmutableDictionary();

internal static string MakeMethodKey(PEMethodSymbol method, ParamInfo<TypeSymbol>[] paramInfo)
{
var pooledBuilder = PooledStringBuilder.GetInstance();
Expand Down Expand Up @@ -65,6 +76,41 @@ internal static string MakeMethodKey(PEMethodSymbol method, ParamInfo<TypeSymbol
// use assembly qualified name format (used in metadata) rather than symbol display?
}

internal static string MakeMethodKey(MethodSymbol method)
{
var pooledBuilder = PooledStringBuilder.GetInstance();

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

Add(method.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?
}

/// <summary>
/// All types in a member which can be annotated should be annotated. Value types and void can be skipped.
/// </summary>
Expand All @@ -73,6 +119,9 @@ internal static string MakeMethodKey(PEMethodSymbol method, ParamInfo<TypeSymbol
private static ImmutableArray<ImmutableArray<bool>> Parameters(params ImmutableArray<bool>[] values)
=> values.ToImmutableArray();

private static ImmutableArray<AttributeDescription> Descriptions(params AttributeDescription[] values)
=> values.ToImmutableArray();

private static ImmutableArray<bool> Nullable(params bool[] values)
{
Debug.Assert(values.Length > 0);
Expand All @@ -96,5 +145,15 @@ private static void Add(TypeSymbol type, StringBuilder builder)
.RemoveMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.UseSpecialTypes)
// displaying tuple syntax causes to load the members of ValueTuple, which can cause a cycle, so we use long-hand format instead
.WithCompilerInternalOptions(SymbolDisplayCompilerInternalOptions.IncludeNullableReferenceTypeModifier | SymbolDisplayCompilerInternalOptions.UseValueTuple)));

internal static ImmutableArray<AttributeDescription> GetExtraAttributes(string key)
{
if (!Attributes.TryGetValue(key, out var descriptions))
{
return default;
}

return descriptions;
}
}
}
Loading

0 comments on commit e66b11f

Please sign in to comment.