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 #3917

Merged
merged 56 commits into from
Aug 26, 2020
Merged
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
9e5e8f1
Platform compatability analyzer draft
buyaa-n Jul 7, 2020
176adea
Support more flow scenarios and add more tests
buyaa-n Jul 9, 2020
9859abe
Enable value content analysis, add few more tests
buyaa-n Jul 9, 2020
0c84c22
Merge conflict
buyaa-n Jul 9, 2020
c7a4bc1
Fix bug caused from merge
buyaa-n Jul 10, 2020
8e84e4e
Merge branch 'master' of https://github.com/dotnet/roslyn-analyzers i…
buyaa-n Jul 10, 2020
42245ba
Enum, property support. Use string comparison ever type, support string
buyaa-n Jul 13, 2020
00ffd24
Excclude conditional check, more refactoring
buyaa-n Jul 17, 2020
5a21e8e
Add/fix tests
buyaa-n Jul 18, 2020
7e7e734
Add more tests
buyaa-n Jul 20, 2020
e80c51a
Osx equals mac and vise versa, add more tests and refactoring
buyaa-n Jul 21, 2020
8c2f910
Apply feedback, more refactoring
buyaa-n Jul 21, 2020
00d17a2
Merge conflict
buyaa-n Jul 21, 2020
84a2fd6
Local function, lambda, even, delegate support and tests
buyaa-n Jul 21, 2020
a375098
Merge conflict
buyaa-n Jul 28, 2020
473ce5c
Support platform name without version
buyaa-n Jul 29, 2020
6ae011e
Apply some review comments
buyaa-n Jul 30, 2020
9a31b5e
Address more feedback
buyaa-n Jul 30, 2020
fb70d09
Add caching, attributes dependency logic
buyaa-n Aug 7, 2020
f210ff4
Rename OS attributes and all references
buyaa-n Aug 7, 2020
3103ec7
Update attributes handling, add/remove new/old guard methods support …
buyaa-n Aug 10, 2020
822ff2c
Fix guard logic with && operator, more refacoring, add more tests
buyaa-n Aug 11, 2020
07a2108
Merge conflict
buyaa-n Aug 11, 2020
6cd038a
Suppress build warning, apply some review comments, add doc/comment
buyaa-n Aug 11, 2020
3916447
Add un versioned diagnostic, add more tests for attributes combinatio…
buyaa-n Aug 12, 2020
21263f2
More tests, review updates
buyaa-n Aug 13, 2020
be8c029
Merging conflict
buyaa-n Aug 13, 2020
28a7cba
Fix bugs 4008, 4009, 4011, apply some feedback, fix failing test
buyaa-n Aug 14, 2020
1eba4b8
Fixing 4012, 4010 and applying more feedback
buyaa-n Aug 14, 2020
63b9156
Perf suggestions
mavasani Aug 15, 2020
7d25d86
Use cache for entire compilation
mavasani Aug 15, 2020
5c9a860
Handle || guards
mavasani Aug 15, 2020
70badce
Fix interprocedural analysis and also restrict when ValueContentAnaly…
mavasani Aug 15, 2020
8d68a29
Lazilly compute needs value content analysis for improved perf
mavasani Aug 15, 2020
c847d9d
Free dictionary after usage remove some unneeded anymore code sections
buyaa-n Aug 15, 2020
c9d1289
Merge branch 'platform_spec_analyzer' of https://github.com/buyaa-n/r…
mavasani Aug 15, 2020
12532fe
Merge conflict
mavasani Aug 15, 2020
7b9276d
Fix bugs in group #2
buyaa-n Aug 16, 2020
60a4dff
Fixed bug #1
buyaa-n Aug 16, 2020
6bc123f
Apply workaround for https://github.com/dotnet/roslyn/issues/46859
mavasani Aug 16, 2020
9988385
Add support override #4013, #4020
buyaa-n Aug 17, 2020
077321c
Merge branch 'platform_spec_analyzer' of github.com:buyaa-n/roslyn-an…
buyaa-n Aug 17, 2020
1cf9990
Update messages, add named arguments support
buyaa-n Aug 17, 2020
56acd8a
Fix an override scenario, update named argument logic
buyaa-n Aug 17, 2020
7b388a1
Apply feedback
buyaa-n Aug 17, 2020
04b6a95
Remove ObsoletedOsPlatform attribute
buyaa-n Aug 18, 2020
919cf2c
Use DisplayString name
buyaa-n Aug 18, 2020
5355797
Apply guard methods new logic fix bug
buyaa-n Aug 19, 2020
d9aa6a9
Add more guard tests and fix failures
buyaa-n Aug 20, 2020
d5e1e74
Add Msbuild configuration support
buyaa-n Aug 21, 2020
865071d
Use local instead of static field
buyaa-n Aug 23, 2020
2dd6240
Fix and enable some failing tests
buyaa-n Aug 24, 2020
5335d58
Match string pattern instead of constants for guard method names
buyaa-n Aug 24, 2020
4aed281
Small refactoring updates
buyaa-n Aug 24, 2020
b749dfb
Apply feedbacks
buyaa-n Aug 24, 2020
c252e9b
Lower case 'platform compatibility'
buyaa-n Aug 25, 2020
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.Shipped.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ CA1308 | Globalization | Disabled | NormalizeStringsToUppercaseAnalyzer, [Docume
CA1309 | Globalization | Hidden | UseOrdinalStringComparisonAnalyzer, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca1309)
CA1401 | Interoperability | Info | PInvokeDiagnosticAnalyzer, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca1401)
CA1414 | Interoperability | Disabled | MarkBooleanPInvokeArgumentsWithMarshalAsAnalyzer, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca1414)
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)
buyaa-n marked this conversation as resolved.
Show resolved Hide resolved
CA1417 | Interoperability | Warning | DoNotUseOutAttributeStringPInvokeParametersAnalyzer, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca1417)
CA1500 | Maintainability | Disabled | VariableNamesShouldNotMatchFieldNamesAnalyzer, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca1500)
CA1501 | Maintainability | Disabled | CodeMetricsAnalyzer, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca1501)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// 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.Diagnostics.CodeAnalysis;

namespace Microsoft.NetCore.Analyzers.InteropServices
{
public sealed partial class PlatformCompatabilityAnalyzer
buyaa-n marked this conversation as resolved.
Show resolved Hide resolved
{
/// <summary>
/// Class used for keeping platform information of an API, all are optional properties
///
/// We need to keep only 2 values for [SupportedOSPlatform] attribute, first one will be the lowest version found, mostly for assembly level attribute which denotes when the API first introduced,
/// second one would keep new APIs added later and requries higher platform version (if there is multiple version found in the API parents chain we will keep only highest version)
buyaa-n marked this conversation as resolved.
Show resolved Hide resolved
///
/// Same for [UnsupportedOSPlatform] attribute, an API could be unsupported at first and then start supported from some version then eventually removed.
/// So we only keep at most 2 versions of [UnsupportedOSPlatform] first one will be the lowest version found, second one will be second lowest if there is any
///
/// I wouldn't expect that [ObsoletedInOSPlatform] attribute used more than once (like obsoleted once, supported back and obsoleted again),
/// so we will keep only one property for that, if any more attrbite found in the API parents chain we will keep the one with lowest versions
///
/// Properties:
/// - SupportedFirst - keeps lowest version of [SupportedOSPlatform] attribute found
/// - SupportedSecond - keeps the highest version of [SupportedOSPlatform] attribute if there is any
/// - UnsupportedFirst - keeps the lowest version of [UnsupportedOSPlatform] attribute found
/// - UnsupportedSecond - keeps the second lowest version of [UnsupportedOSPlatform] attribute found
/// - Obsoleted - keeps lowest version of [ObsoletedInOSPlatform] attrbite found
/// </summary>
private class PlatformAttributes
{
public Version? Obsoleted { get; set; }
buyaa-n marked this conversation as resolved.
Show resolved Hide resolved
public Version? SupportedFirst { get; set; }
public Version? SupportedSecond { get; set; }
public Version? UnsupportedFirst { get; set; }
public Version? UnsupportedSecond { get; set; }
buyaa-n marked this conversation as resolved.
Show resolved Hide resolved
public bool HasAttribute() => SupportedFirst != null || UnsupportedFirst != null ||
SupportedSecond != null || UnsupportedSecond != null || Obsoleted != null;
}

private static bool TryParsePlatformNameAndVersion(string osString, out string osPlatformName, [NotNullWhen(true)] out Version? version)
buyaa-n marked this conversation as resolved.
Show resolved Hide resolved
{
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;
}

return false;
}
}

osPlatformName = osString;
version = new Version(0, 0);
return true;
}
}
}
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 sealed partial class PlatformCompatabilityAnalyzer
{
private sealed class OperationVisitor : GlobalFlowStateDataFlowOperationVisitor
{
Expand All @@ -34,16 +34,35 @@ 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))
{
return RuntimeOSPlatformInfo.TryDecode(method, visitedArguments, DataFlowAnalysisContext.ValueContentAnalysisResult, _osPlatformType, out var platformInfo) ?
return RuntimeMethodValue.TryDecode(method, visitedArguments, DataFlowAnalysisContext.ValueContentAnalysisResult, _osPlatformType, out var platformInfo) ?
new GlobalFlowStateAnalysisValueSet(platformInfo) :
GlobalFlowStateAnalysisValueSet.Unknown;
}

return GetValueOrDefault(value);
}

public override GlobalFlowStateAnalysisValueSet VisitPropertyReference(IPropertyReferenceOperation operation, object? argument)
{
buyaa-n marked this conversation as resolved.
Show resolved Hide resolved
return GetValueOrDefault(base.VisitPropertyReference(operation, argument));
}

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

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

public override GlobalFlowStateAnalysisValueSet VisitEventReference(IEventReferenceOperation operation, object? argument)
{
return GetValueOrDefault(base.VisitEventReference(operation, argument));
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
// 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;
using Analyzer.Utilities.PooledObjects;
using System.Linq;
using Microsoft.CodeAnalysis.FlowAnalysis.DataFlow;
using Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.GlobalFlowStateAnalysis;
using Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.ValueContentAnalysis;
using Microsoft.CodeAnalysis.Operations;
using Microsoft.CodeAnalysis;
using System.Diagnostics.CodeAnalysis;
using Analyzer.Utilities;
buyaa-n marked this conversation as resolved.
Show resolved Hide resolved
using System.Diagnostics;
using Analyzer.Utilities.Extensions;

namespace Microsoft.NetCore.Analyzers.InteropServices
{
using ValueContentAnalysisResult = DataFlowAnalysisResult<ValueContentBlockAnalysisResult, ValueContentAbstractValue>;

public sealed partial class PlatformCompatabilityAnalyzer
{
private const string Browser = nameof(Browser);
private const string Linux = nameof(Linux);
private const string FreeBSD = nameof(FreeBSD);
private const string Android = nameof(Android);
private const string IOS = nameof(IOS);
private const string MacOS = nameof(MacOS);
private const string TvOS = nameof(TvOS);
private const string WatchOS = nameof(WatchOS);
private const string Windows = nameof(Windows);
buyaa-n marked this conversation as resolved.
Show resolved Hide resolved

private readonly struct RuntimeMethodValue : IAbstractAnalysisValue, IEquatable<RuntimeMethodValue>
{
private RuntimeMethodValue(string invokedPlatformCheckMethodName, 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 RuntimeMethodValue(InvokedMethodName, PlatformName, Version, !Negated);

public static bool TryDecode(
IMethodSymbol invokedPlatformCheckMethod,
ImmutableArray<IArgumentOperation> arguments,
ValueContentAnalysisResult? valueContentAnalysisResult,
INamedTypeSymbol osPlatformType,
[NotNullWhen(returnValue: true)] out RuntimeMethodValue? info)
{
// Accelerators like OperatingSystem.IsPlatformName()
if (arguments.IsEmpty)
{
var platformName = SwitchPlatformName(invokedPlatformCheckMethod.Name);
buyaa-n marked this conversation as resolved.
Show resolved Hide resolved
if (platformName != null)
{
info = new RuntimeMethodValue(invokedPlatformCheckMethod.Name, platformName, new Version(0, 0), negated: false);
return true;
}
}
else
{
if (TryDecodeRuntimeInformationIsOSPlatform(arguments[0].Value, osPlatformType, out string? osPlatformName))
{
info = new RuntimeMethodValue(invokedPlatformCheckMethod.Name, osPlatformName, new Version(0, 0), negated: false);
return true;
}

if (arguments.GetArgumentForParameterAtIndex(0).Value is ILiteralOperation literal)
{
if (literal.Type?.SpecialType == SpecialType.System_String &&
literal.ConstantValue.HasValue)
{
// OperatingSystem.IsOSPlatform(string platform)
if (invokedPlatformCheckMethod.Name == IsOSPlatform &&
TryParsePlatformNameAndVersion(literal.ConstantValue.Value.ToString(), out string platformName, out Version? version))
{
info = new RuntimeMethodValue(invokedPlatformCheckMethod.Name, platformName, version, negated: false);
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);
info = new RuntimeMethodValue(invokedPlatformCheckMethod.Name, literal.ConstantValue.Value.ToString(), version, negated: false);
return true;
}
}
else if (literal.Type?.SpecialType == SpecialType.System_Int32)
{
// Accelerators like OperatingSystem.IsPlatformNameVersionAtLeast(int major, int minor = 0, int build = 0, int revision = 0)
var platformName = SwitchVersionedPlatformName(invokedPlatformCheckMethod.Name);

if (platformName != null && TryDecodeOSVersion(arguments, valueContentAnalysisResult, out var version))
{
info = new RuntimeMethodValue(invokedPlatformCheckMethod.Name, platformName, version, negated: false);
return true;
}
}
}
}

info = default;
return false;
}

private static string? SwitchVersionedPlatformName(string methodName)
buyaa-n marked this conversation as resolved.
Show resolved Hide resolved
=> methodName switch
{
IsWindowsVersionAtLeast => Windows,
IsMacOSVersionAtLeast => MacOS,
IsIOSVersionAtLeast => IOS,
IsAndroidVersionAtLeast => Android,
IsFreeBSDVersionAtLeast => FreeBSD,
IsTvOSVersionAtLeast => TvOS,
IsWatchOSVersionAtLeast => WatchOS,
_ => null
};

private static string? SwitchPlatformName(string methodName)
=> methodName switch
{
IsWindows => Windows,
IsLinux => Linux,
IsMacOS => MacOS,
IsIOS => IOS,
IsBrowser => Browser,
IsAndroid => Android,
IsFreeBSD => FreeBSD,
IsTvOS => TvOS,
IsWatchOS => WatchOS,
_ => null
};
buyaa-n marked this conversation as resolved.
Show resolved Hide resolved

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

osPlatformName = null;
return false;
}

private static bool TryDecodeOSVersion(
ImmutableArray<IArgumentOperation> arguments,
ValueContentAnalysisResult? valueContentAnalysisResult,
[NotNullWhen(returnValue: true)] out Version? osVersion,
int skip = 0)
{

using var versionBuilder = ArrayBuilder<int>.GetInstance(4, fillWithValue: 0);
var index = 0;

while (index < arguments.Length - skip)
{
if (!TryDecodeOSVersionPart(arguments.GetArgumentForParameterAtIndex(index + skip), valueContentAnalysisResult, out var osVersionPart))
{
osVersion = null;
return false;
}

versionBuilder[index++] = osVersionPart;
}

osVersion = CreateVersion(versionBuilder);

return true;

static bool TryDecodeOSVersionPart(IArgumentOperation argument, ValueContentAnalysisResult? valueContentAnalysisResult, out int osVersionPart)
{
if (argument.Value.ConstantValue.HasValue &&
argument.Value.ConstantValue.Value is int versionPart)
{
osVersionPart = versionPart;
return true;
}

if (valueContentAnalysisResult != null)
{
var valueContentValue = valueContentAnalysisResult[argument.Value];
if (valueContentValue.IsLiteralState &&
valueContentValue.LiteralValues.Count == 1 &&
valueContentValue.LiteralValues.Single() is int part)
{
osVersionPart = part;
return true;
}
}

osVersionPart = default;
return false;
}

static Version CreateVersion(ArrayBuilder<int> versionBuilder)
buyaa-n marked this conversation as resolved.
Show resolved Hide resolved
{
if (versionBuilder[3] == 0)
{
if (versionBuilder[2] == 0)
{
return new Version(versionBuilder[0], versionBuilder[1]);
}
else
{
return new Version(versionBuilder[0], versionBuilder[1], versionBuilder[2]);
}
}
else
{
return new Version(versionBuilder[0], versionBuilder[1], versionBuilder[2], versionBuilder[3]);
}
buyaa-n marked this conversation as resolved.
Show resolved Hide resolved
}
}

public override string ToString()
{
var result = $"{InvokedMethodName};{PlatformName};{Version}";
if (Negated)
{
result = $"!{result}";
}

return result;
}

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

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

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

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

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

public static bool operator !=(RuntimeMethodValue left, RuntimeMethodValue right)
{
return !(left == right);
}
}
}
}
Loading