Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CA1416 Support custom guard members annotated with guard attributes #5087

Merged
merged 6 commits into from
May 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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; }
buyaa-n marked this conversation as resolved.
Show resolved Hide resolved
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