Skip to content

Commit

Permalink
CA1416 Support custom guard members annotated with guard attributes (#…
Browse files Browse the repository at this point in the history
…5087)

* Support custom guard with attributes
  • Loading branch information
buyaa-n authored May 12, 2021
1 parent 516079f commit 15dd5af
Show file tree
Hide file tree
Showing 5 changed files with 624 additions and 54 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// 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.Collections.Immutable;
using System.Linq;
using Analyzer.Utilities.Extensions;
using Analyzer.Utilities.PooledObjects;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.GlobalFlowStateAnalysis;
Expand All @@ -25,6 +27,38 @@ public OperationVisitor(
_osPlatformType = osPlatformType;
}

internal static bool TryParseGuardAttributes(ISymbol symbol, ref GlobalFlowStateAnalysisValueSet value)
{
var attributes = symbol.GetAttributes();

if (symbol.GetMemberType()!.SpecialType != SpecialType.System_Boolean ||
!HasAnyGuardAttribute(attributes))
{
return false;
}

using var infosBuilder = ArrayBuilder<PlatformMethodValue>.GetInstance();
if (PlatformMethodValue.TryDecode(attributes, infosBuilder))
{
for (var i = 0; i < infosBuilder.Count; i++)
{
var newValue = GlobalFlowStateAnalysisValueSet.Create(infosBuilder[i]);
// if the incoming value is negated it should be merged with AND logic, else with OR.
value = i == 0 ? newValue : infosBuilder[i].Negated ? value.WithAdditionalAnalysisValues(newValue, false) :
GlobalFlowStateAnalysis.GlobalFlowStateAnalysisValueSetDomain.Instance.Merge(value, newValue);
}

return true;
}

value = GlobalFlowStateAnalysisValueSet.Unknown;

return false;

static bool HasAnyGuardAttribute(ImmutableArray<AttributeData> attributes) =>
attributes.Any(a => a.AttributeClass.Name is SupportedOSPlatformGuardAttribute or UnsupportedOSPlatformGuardAttribute);
}

public override GlobalFlowStateAnalysisValueSet VisitInvocation_NonLambdaOrDelegateOrLocalFunction(
IMethodSymbol method,
IOperation? visitedInstance,
Expand All @@ -51,9 +85,37 @@ public override GlobalFlowStateAnalysisValueSet VisitInvocation_NonLambdaOrDeleg

return GlobalFlowStateAnalysisValueSet.Unknown;
}
else if (TryParseGuardAttributes(method, ref value))
{
return value;
}

return value;
}

public override GlobalFlowStateAnalysisValueSet VisitFieldReference(IFieldReferenceOperation operation, object? argument)
{
var value = base.VisitFieldReference(operation, argument);

if (TryParseGuardAttributes(operation.Field, ref value))
{
return value;
}

return ComputeAnalysisValueForReferenceOperation(operation, value);
}

public override GlobalFlowStateAnalysisValueSet VisitPropertyReference(IPropertyReferenceOperation operation, object? argument)
{
var value = base.VisitPropertyReference(operation, argument);

if (TryParseGuardAttributes(operation.Property, ref value))
{
return value;
}

return ComputeAnalysisValueForReferenceOperation(operation, value);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,19 @@ public sealed partial class PlatformCompatibilityAnalyzer
{
private readonly struct PlatformMethodValue : IAbstractAnalysisValue, IEquatable<PlatformMethodValue>
{
private PlatformMethodValue(string invokedPlatformCheckMethodName, string platformPropertyName, Version version, bool negated)
internal PlatformMethodValue(string platformPropertyName, Version version, bool negated)
{
InvokedMethodName = invokedPlatformCheckMethodName ?? throw new ArgumentNullException(nameof(invokedPlatformCheckMethodName));
PlatformName = platformPropertyName ?? throw new ArgumentNullException(nameof(platformPropertyName));
Version = version ?? throw new ArgumentNullException(nameof(version));
Negated = negated;
}

public string InvokedMethodName { get; }
public string PlatformName { get; }
public Version Version { get; }
public bool Negated { get; }

public IAbstractAnalysisValue GetNegatedValue()
=> new PlatformMethodValue(InvokedMethodName, PlatformName, Version, !Negated);
=> new PlatformMethodValue(PlatformName, Version, !Negated);

public static bool TryDecode(
IMethodSymbol invokedPlatformCheckMethod,
Expand All @@ -50,7 +48,7 @@ public static bool TryDecode(
{
if (TryExtractPlatformName(invokedPlatformCheckMethod.Name, out var platformName))
{
var info = new PlatformMethodValue(invokedPlatformCheckMethod.Name, platformName, new Version(0, 0), negated: false);
var info = new PlatformMethodValue(platformName, EmptyVersion, negated: false);
infosBuilder.Add(info);
return true;
}
Expand All @@ -63,7 +61,7 @@ public static bool TryDecode(
Debug.Assert(osPlatformNamesBuilder.Count > 0);
for (var i = 0; i < osPlatformNamesBuilder.Count; i++)
{
var info = new PlatformMethodValue(invokedPlatformCheckMethod.Name, osPlatformNamesBuilder[i], new Version(0, 0), negated: false);
var info = new PlatformMethodValue(osPlatformNamesBuilder[i], EmptyVersion, negated: false);
infosBuilder.Add(info);
}

Expand All @@ -79,15 +77,15 @@ public static bool TryDecode(
if (invokedPlatformCheckMethod.Name == IsOSPlatform &&
TryParsePlatformNameAndVersion(literal.ConstantValue.Value.ToString(), out string platformName, out Version? version))
{
var info = new PlatformMethodValue(invokedPlatformCheckMethod.Name, platformName, version, negated: false);
var info = new PlatformMethodValue(platformName, version, negated: false);
infosBuilder.Add(info);
return true;
}
else if (TryDecodeOSVersion(arguments, valueContentAnalysisResult, out version, 1))
{
// OperatingSystem.IsOSPlatformVersionAtLeast(string platform, int major, int minor = 0, int build = 0, int revision = 0)
Debug.Assert(invokedPlatformCheckMethod.Name == "IsOSPlatformVersionAtLeast");
var info = new PlatformMethodValue(invokedPlatformCheckMethod.Name, literal.ConstantValue.Value.ToString(), version, negated: false);
var info = new PlatformMethodValue(literal.ConstantValue.Value.ToString(), version, negated: false);
infosBuilder.Add(info);
return true;
}
Expand All @@ -98,7 +96,7 @@ public static bool TryDecode(
if (TryExtractPlatformName(invokedPlatformCheckMethod.Name, out var platformName) &&
TryDecodeOSVersion(arguments, valueContentAnalysisResult, out var version))
{
var info = new PlatformMethodValue(invokedPlatformCheckMethod.Name, platformName, version, negated: false);
var info = new PlatformMethodValue(platformName, version, negated: false);
infosBuilder.Add(info);
return true;
}
Expand All @@ -109,6 +107,20 @@ public static bool TryDecode(
return false;
}

public static bool TryDecode(ImmutableArray<AttributeData> attributes, ArrayBuilder<PlatformMethodValue> infosBuilder)
{
foreach (var attribute in attributes)
{
if (attribute.AttributeClass.Name is SupportedOSPlatformGuardAttribute or UnsupportedOSPlatformGuardAttribute &&
TryParsePlatformNameAndVersion(attribute, out var platformName, out var version))
{
var info = new PlatformMethodValue(platformName, version, negated: attribute.AttributeClass.Name == UnsupportedOSPlatformGuardAttribute);
infosBuilder.Add(info);
}
}
return infosBuilder.Any();
}

private static bool TryExtractPlatformName(string methodName, [NotNullWhen(true)] out string? platformName)
{
if (!methodName.StartsWith(IsPrefix, StringComparison.Ordinal))
Expand Down Expand Up @@ -225,7 +237,7 @@ static Version CreateVersion(ArrayBuilder<int> versionBuilder)

public override string ToString()
{
var result = $"{InvokedMethodName};{PlatformName};{Version}";
var result = $"{PlatformName};{Version}";
if (Negated)
{
result = $"!{result}";
Expand All @@ -235,18 +247,16 @@ public override string ToString()
}

public bool Equals(PlatformMethodValue other)
=> InvokedMethodName.Equals(other.InvokedMethodName, StringComparison.OrdinalIgnoreCase) &&
PlatformName.Equals(other.PlatformName, StringComparison.OrdinalIgnoreCase) &&
Version.Equals(other.Version) &&
Negated == other.Negated;
=> PlatformName.Equals(other.PlatformName, StringComparison.OrdinalIgnoreCase) &&
Version.Equals(other.Version) &&
Negated == other.Negated;

public override bool Equals(object obj)
=> obj is PlatformMethodValue otherInfo && Equals(otherInfo);

public override int GetHashCode()
{
return RoslynHashCode.Combine(
InvokedMethodName.GetHashCode(),
PlatformName.GetHashCode(),
Version.GetHashCode(),
Negated.GetHashCode());
Expand Down
Loading

0 comments on commit 15dd5af

Please sign in to comment.