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

Platform compatibility analyzer draft #3

Closed
wants to merge 19 commits into from
Closed
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
2 changes: 1 addition & 1 deletion src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ CA1046 | Design | Disabled | DoNotOverloadOperatorEqualsOnReferenceTypes, [Docum
CA1047 | Design | Info | DoNotDeclareProtectedMembersInSealedTypes, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca1047)
CA1069 | Design | Info | EnumShouldNotHaveDuplicatedValues, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca1069)
CA1070 | Design | Info | DoNotDeclareEventFieldsAsVirtual, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca1070)
CA1416 | Interoperability | Info | RuntimePlatformCheckAnalyzer, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca1416)
CA1416 | Interoperability | Warning | PlatformCompatabilityAnalyzer, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca1416)
CA1417 | Interoperability | Warning | DoNotUseOutAttributeStringPinvokeParametersAnalyzer, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca1417)
CA1700 | Naming | Disabled | DoNotNameEnumValuesReserved, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca1700)
CA1713 | Naming | Disabled | EventsShouldNotHaveBeforeOrAfterPrefix, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca1713)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// 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.Immutable;
Expand All @@ -17,11 +17,98 @@ namespace Microsoft.NetCore.Analyzers.InteropServices
{
using ValueContentAnalysisResult = DataFlowAnalysisResult<ValueContentBlockAnalysisResult, ValueContentAbstractValue>;

public sealed partial class RuntimePlatformCheckAnalyzer
public partial class PlatformCompatabilityAnalyzer
{
private struct RuntimeOSPlatformInfo : IAbstractAnalysisValue, IEquatable<RuntimeOSPlatformInfo>
private enum PlatformAttributeType
{
private RuntimeOSPlatformInfo(string invokedPlatformCheckMethodName, string platformPropertyName, Version version, bool negated)
None, MinimumOSPlatformAttribute, ObsoletedInOSPlatformAttribute, RemovedInOSPlatformAttribute, TargetPlatformAttribute
}

private struct PlatformAttributeInfo : IEquatable<PlatformAttributeInfo>
{
public PlatformAttributeType AttributeType { get; set; }
public string PlatformName { get; set; }
public Version Version { get; set; }

public static bool TryParsePlatformAttributeInfo(AttributeData osAttribute, out PlatformAttributeInfo parsedAttribute)
{
parsedAttribute = new PlatformAttributeInfo();
switch (osAttribute.AttributeClass.Name)
{
case MinimumOSPlatformAttribute:
parsedAttribute.AttributeType = PlatformAttributeType.MinimumOSPlatformAttribute;
break;
case ObsoletedInOSPlatformAttribute:
parsedAttribute.AttributeType = PlatformAttributeType.ObsoletedInOSPlatformAttribute;
break;
case RemovedInOSPlatformAttribute:
parsedAttribute.AttributeType = PlatformAttributeType.RemovedInOSPlatformAttribute;
break;
case TargetPlatformAttribute:
parsedAttribute.AttributeType = PlatformAttributeType.TargetPlatformAttribute;
break;
default:
parsedAttribute.AttributeType = PlatformAttributeType.None;
break;
}

if (osAttribute.ConstructorArguments.Length == 1 && osAttribute.ConstructorArguments[0].Type.SpecialType == SpecialType.System_String &&
!osAttribute.ConstructorArguments[0].IsNull && TryParsePlatformNameAndVersion(osAttribute.ConstructorArguments[0].Value.ToString(), out string platformName, out Version? version))
{
parsedAttribute.PlatformName = platformName;
parsedAttribute.Version = version;
return true;
}

return false;
}

public override bool Equals(object obj)
{
if (obj is PlatformAttributeInfo info)
{
return Equals(info);
}
return false;
}

public override int GetHashCode() => HashUtilities.Combine(AttributeType.GetHashCode(), PlatformName.GetHashCode(), Version.GetHashCode());

public static bool operator ==(PlatformAttributeInfo left, PlatformAttributeInfo right) => left.Equals(right);

public static bool operator !=(PlatformAttributeInfo left, PlatformAttributeInfo right) => !(left == right);

public bool Equals(PlatformAttributeInfo other) =>
AttributeType == other.AttributeType && IsOSPlatformsEqual(PlatformName, other.PlatformName) && Version.Equals(other.Version);
}

private static bool TryParsePlatformNameAndVersion(string osString, out string osPlatformName, [NotNullWhen(true)] out Version? version)
{
version = null;
osPlatformName = string.Empty;
for (int i = 0; i < osString.Length; i++)
{
if (char.IsDigit(osString[i]))
{
if (i > 0 && Version.TryParse(osString.Substring(i), out Version? parsedVersion))
{
osPlatformName = osString.Substring(0, i);
version = parsedVersion;
return true;
}
else
{
break;
}
}
}

return false;
}

private struct RuntimeMethodValue : IAbstractAnalysisValue, IEquatable<RuntimeMethodValue>
{
private RuntimeMethodValue(string invokedPlatformCheckMethodName, string platformPropertyName, Version version, bool negated)
{
InvokedPlatformCheckMethodName = invokedPlatformCheckMethodName ?? throw new ArgumentNullException(nameof(invokedPlatformCheckMethodName));
PlatformPropertyName = platformPropertyName ?? throw new ArgumentNullException(nameof(platformPropertyName));
Expand All @@ -35,49 +122,59 @@ private RuntimeOSPlatformInfo(string invokedPlatformCheckMethodName, string plat
public bool Negated { get; }

public IAbstractAnalysisValue GetNegatedValue()
=> new RuntimeOSPlatformInfo(InvokedPlatformCheckMethodName, PlatformPropertyName, Version, !Negated);
=> new RuntimeMethodValue(InvokedPlatformCheckMethodName, PlatformPropertyName, Version, !Negated);

public static bool TryDecode(
IMethodSymbol invokedPlatformCheckMethod,
ImmutableArray<IArgumentOperation> arguments,
ValueContentAnalysisResult? valueContentAnalysisResult,
INamedTypeSymbol osPlatformType,
[NotNullWhen(returnValue: true)] out RuntimeOSPlatformInfo? info)
[NotNullWhen(returnValue: true)] out RuntimeMethodValue? info)
{
if (!TryDecodeOSPlatform(arguments, osPlatformType, out var osPlatformProperty) ||
Debug.Assert(!arguments.IsEmpty);

if (arguments[0].Value is ILiteralOperation literal && literal.Type.SpecialType == SpecialType.System_String)
{
if (literal.ConstantValue.HasValue && TryParsePlatformNameAndVersion(literal.ConstantValue.Value.ToString(), out string platformName, out Version? version))
{
info = new RuntimeMethodValue(invokedPlatformCheckMethod.Name, platformName, version, negated: false);
return true;
}
}

if (!TryDecodeOSPlatform(arguments, osPlatformType, out var osPlatformName) ||
!TryDecodeOSVersion(arguments, valueContentAnalysisResult, out var osVersion))
{
// Bail out
info = default;
return false;
}

info = new RuntimeOSPlatformInfo(invokedPlatformCheckMethod.Name, osPlatformProperty.Name, osVersion, negated: false);
info = new RuntimeMethodValue(invokedPlatformCheckMethod.Name, osPlatformName, osVersion, negated: false);
return true;
}

private static bool TryDecodeOSPlatform(
ImmutableArray<IArgumentOperation> arguments,
INamedTypeSymbol osPlatformType,
[NotNullWhen(returnValue: true)] out IPropertySymbol? osPlatformProperty)
[NotNullWhen(returnValue: true)] out string? osPlatformName)
{
Debug.Assert(!arguments.IsEmpty);
return TryDecodeOSPlatform(arguments[0].Value, osPlatformType, out osPlatformProperty);
return TryDecodeOSPlatform(arguments[0].Value, osPlatformType, out osPlatformName);
}

private static bool TryDecodeOSPlatform(
IOperation argumentValue,
INamedTypeSymbol osPlatformType,
[NotNullWhen(returnValue: true)] out IPropertySymbol? osPlatformProperty)
[NotNullWhen(returnValue: true)] out string? osPlatformName)
{
if ((argumentValue is IPropertyReferenceOperation propertyReference) &&
propertyReference.Property.ContainingType.Equals(osPlatformType))
{
osPlatformProperty = propertyReference.Property;
osPlatformName = propertyReference.Property.Name;
return true;
}

osPlatformProperty = null;
osPlatformName = null;
return false;
}

Expand Down Expand Up @@ -130,60 +227,39 @@ static bool TryDecodeOSVersionPart(IArgumentOperation argument, ValueContentAnal

public override string ToString()
{
var versionStr = Version.ToString(fieldCount: GetVersionFieldCount(Version));
var result = $"{InvokedPlatformCheckMethodName};{PlatformPropertyName};{versionStr}";
var result = $"{InvokedPlatformCheckMethodName};{PlatformPropertyName};{Version}";
if (Negated)
{
result = $"!{result}";
}

return result;

static int GetVersionFieldCount(Version version)
{
if (version.Revision != 0)
{
return 4;
}

if (version.Build != 0)
{
return 3;
}

if (version.Minor != 0)
{
return 2;
}

return 1;
}
}

public bool Equals(RuntimeOSPlatformInfo other)
public bool Equals(RuntimeMethodValue other)
=> InvokedPlatformCheckMethodName.Equals(other.InvokedPlatformCheckMethodName, StringComparison.OrdinalIgnoreCase) &&
PlatformPropertyName.Equals(other.PlatformPropertyName, StringComparison.OrdinalIgnoreCase) &&
Version.Equals(other.Version) &&
Negated == other.Negated;

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

public override int GetHashCode()
=> HashUtilities.Combine(InvokedPlatformCheckMethodName.GetHashCode(), PlatformPropertyName.GetHashCode(), Version.GetHashCode(), Negated.GetHashCode());

bool IEquatable<IAbstractAnalysisValue>.Equals(IAbstractAnalysisValue other)
=> other is RuntimeOSPlatformInfo otherInfo && Equals(otherInfo);
=> other is RuntimeMethodValue otherInfo && Equals(otherInfo);

public static bool operator ==(RuntimeOSPlatformInfo left, RuntimeOSPlatformInfo right)
public static bool operator ==(RuntimeMethodValue left, RuntimeMethodValue right)
{
return left.Equals(right);
}

public static bool operator !=(RuntimeOSPlatformInfo left, RuntimeOSPlatformInfo right)
public static bool operator !=(RuntimeMethodValue left, RuntimeMethodValue right)
{
return !(left == right);
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace Microsoft.NetCore.Analyzers.InteropServices
{
public sealed partial class RuntimePlatformCheckAnalyzer
public partial class PlatformCompatabilityAnalyzer
{
private sealed class OperationVisitor : GlobalFlowStateDataFlowOperationVisitor
{
Expand All @@ -34,16 +34,39 @@ public override GlobalFlowStateAnalysisValueSet VisitInvocation_NonLambdaOrDeleg
{
var value = base.VisitInvocation_NonLambdaOrDelegateOrLocalFunction(method, visitedInstance, visitedArguments, invokedAsDelegate, originalOperation, defaultValue);

if (_platformCheckMethods.Contains(method.OriginalDefinition) &&
!visitedArguments.IsEmpty)
if (_platformCheckMethods.Contains(method.OriginalDefinition) && !visitedArguments.IsEmpty)
{
return RuntimeOSPlatformInfo.TryDecode(method, visitedArguments, DataFlowAnalysisContext.ValueContentAnalysisResultOpt, _osPlatformType, out var platformInfo) ?
return RuntimeMethodValue.TryDecode(method, visitedArguments, DataFlowAnalysisContext.ValueContentAnalysisResultOpt, _osPlatformType, out var platformInfo) ?
new GlobalFlowStateAnalysisValueSet(platformInfo) :
GlobalFlowStateAnalysisValueSet.Unknown;
}

return GetValueOrDefault(value);
}

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

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

public override GlobalFlowStateAnalysisValueSet VisitObjectCreation(IObjectCreationOperation operation, object? argument)
{
var value = base.VisitObjectCreation(operation, argument);
return GetValueOrDefault(value);
}

public override GlobalFlowStateAnalysisValueSet VisitEventReference(IEventReferenceOperation operation, object? argument)
{
var value = base.VisitEventReference(operation, argument);
return GetValueOrDefault(value);
}
}
}
}
Loading