Skip to content

Commit

Permalink
[CA1416] Targeted build attributes can be overriden in cross platform…
Browse files Browse the repository at this point in the history
… assemblies (#5117)

* Targeted build attributes can be overriden in cross platform assemblies
  • Loading branch information
buyaa-n authored May 27, 2021
1 parent a43657b commit 5078028
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public PlatformAttributes(Callsite callsite, SmallDictionary<string, Versions> p

public SmallDictionary<string, Versions>? Platforms { get; set; }
public Callsite Callsite { get; set; }
public bool IsAssemblyAttribute { get; set; }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -172,9 +172,10 @@ public override void Initialize(AnalysisContext context)
m.Parameters.Length == 1 &&
m.Parameters[0].Type.SpecialType == SpecialType.System_String);
var notSupportedExceptionType = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemNotSupportedException);
var crossPlatform = HasCrossPlatformProperty(context.Options, context.Compilation, context.CancellationToken);

context.RegisterOperationBlockStartAction(
context => AnalyzeOperationBlock(context, guardMethods, runtimeIsOSPlatformMethod, osPlatformCreateMethod,
context => AnalyzeOperationBlock(context, guardMethods, runtimeIsOSPlatformMethod, osPlatformCreateMethod, crossPlatform,
osPlatformType, stringType, platformSpecificMembers, msBuildPlatforms, notSupportedExceptionType));
});

Expand All @@ -199,6 +200,11 @@ static ImmutableArray<string> GetSupportedPlatforms(AnalyzerOptions options, Com

static bool NameAndParametersValid(IMethodSymbol method) => method.Name.StartsWith(IsPrefix, StringComparison.Ordinal) &&
(method.Parameters.Length == 0 || method.Name.EndsWith(OptionalSuffix, StringComparison.Ordinal));

static bool HasCrossPlatformProperty(AnalyzerOptions options, Compilation compilation, CancellationToken cancellationToken)
{
return options.GetMSBuildPropertyValue(MSBuildPropertyOptionNames.PlatformNeutralAssembly, compilation, cancellationToken) is not null;
}
}

private static bool PlatformAnalysisAllowed(AnalyzerOptions options, Compilation compilation, CancellationToken token)
Expand Down Expand Up @@ -227,6 +233,7 @@ private void AnalyzeOperationBlock(
ImmutableArray<IMethodSymbol> guardMethods,
IMethodSymbol? runtimeIsOSPlatformMethod,
IMethodSymbol? osPlatformCreateMethod,
bool crossPlatform,
INamedTypeSymbol? osPlatformType,
INamedTypeSymbol stringType,
ConcurrentDictionary<ISymbol, PlatformAttributes> platformSpecificMembers,
Expand All @@ -244,7 +251,7 @@ private void AnalyzeOperationBlock(

context.RegisterOperationAction(context =>
{
AnalyzeOperation(context.Operation, context, platformSpecificOperations, platformSpecificMembers, msBuildPlatforms, notSupportedExceptionType);
AnalyzeOperation(context.Operation, context, platformSpecificOperations, platformSpecificMembers, msBuildPlatforms, notSupportedExceptionType, crossPlatform);
},
OperationKind.MethodReference,
OperationKind.EventReference,
Expand Down Expand Up @@ -627,14 +634,15 @@ static void ReportSupportedDiagnostic(IOperation operation, OperationBlockAnalys
{
var supportedRule = GetSupportedPlatforms(attributes, callsiteAttributes, out var platformNames);
var callSitePlatforms = GetCallsitePlatforms(attributes, callsiteAttributes, out var callsite, supported: supportedRule);
var csPlatformNames = JoinNames(callSitePlatforms);

if (callsite == Callsite.Reachable && IsDenyList(callsiteAttributes))
{
callSitePlatforms.Add(MicrosoftNetCoreAnalyzersResources.PlatformCompatibilityAllPlatforms);
csPlatformNames = string.Join(MicrosoftNetCoreAnalyzersResources.CommaSeparator, csPlatformNames, MicrosoftNetCoreAnalyzersResources.PlatformCompatibilityAllPlatforms);
}

var rule = supportedRule ? SwitchSupportedRule(callsite) : SwitchRule(callsite, true);
context.ReportDiagnostic(operation.CreateDiagnostic(rule, operationName, JoinNames(platformNames), JoinNames(callSitePlatforms)));
context.ReportDiagnostic(operation.CreateDiagnostic(rule, operationName, JoinNames(platformNames), csPlatformNames));

static DiagnosticDescriptor SwitchSupportedRule(Callsite callsite)
=> callsite switch
Expand Down Expand Up @@ -907,7 +915,11 @@ static string AddOsxIfMacOS(string platformName) =>

static string EncloseWithQuotes(string pName) => $"'{AddOsxIfMacOS(pName)}'";

static string JoinNames(List<string> platformNames) => string.Join(MicrosoftNetCoreAnalyzersResources.CommaSeparator, platformNames);
static string JoinNames(List<string> platformNames)
{
platformNames.Sort(StringComparer.OrdinalIgnoreCase);
return string.Join(MicrosoftNetCoreAnalyzersResources.CommaSeparator, platformNames);
}

static SymbolDisplayFormat GetLanguageSpecificFormat(IOperation operation) =>
operation.Language == LanguageNames.CSharp ? SymbolDisplayFormat.CSharpShortErrorMessageFormat : SymbolDisplayFormat.VisualBasicShortErrorMessageFormat;
Expand Down Expand Up @@ -1004,7 +1016,7 @@ private static ISymbol GetEventAccessor(IEventSymbol iEvent, IOperation operatio
private static void AnalyzeOperation(IOperation operation, OperationAnalysisContext context, PooledConcurrentDictionary<KeyValuePair<IOperation, ISymbol>,
(SmallDictionary<string, Versions> attributes, SmallDictionary<string, Versions>? csAttributes)> platformSpecificOperations,
ConcurrentDictionary<ISymbol, PlatformAttributes> platformSpecificMembers, ImmutableArray<string> msBuildPlatforms,
ITypeSymbol? notSupportedExceptionType)
ITypeSymbol? notSupportedExceptionType, bool crossPlatform)
{
if (operation.Parent is IArgumentOperation argumentOperation && UsedInCreatingNotSupportedException(argumentOperation, notSupportedExceptionType))
{
Expand All @@ -1018,15 +1030,15 @@ private static void AnalyzeOperation(IOperation operation, OperationAnalysisCont
return;
}

CheckOperationAttributes(operation, context, platformSpecificOperations, platformSpecificMembers, msBuildPlatforms, symbol, checkParents: true);
CheckOperationAttributes(symbol, checkParents: true);

if (symbol is IPropertySymbol property)
{
foreach (var accessor in GetPropertyAccessors(property, operation))
{
if (accessor != null)
{
CheckOperationAttributes(operation, context, platformSpecificOperations, platformSpecificMembers, msBuildPlatforms, accessor, checkParents: false);
CheckOperationAttributes(accessor, checkParents: false);
}
}
}
Expand All @@ -1036,32 +1048,26 @@ private static void AnalyzeOperation(IOperation operation, OperationAnalysisCont

if (accessor != null)
{
CheckOperationAttributes(operation, context, platformSpecificOperations, platformSpecificMembers, msBuildPlatforms, accessor, checkParents: false);
CheckOperationAttributes(accessor, checkParents: false);
}
}
else if (symbol is IMethodSymbol method && method.IsGenericMethod)
{
CheckTypeArguments(method.TypeArguments, operation, context, platformSpecificOperations, platformSpecificMembers, msBuildPlatforms);
CheckTypeArguments(method.TypeArguments);
}

if (symbol.ContainingSymbol is INamedTypeSymbol namedType && namedType.IsGenericType)
{
CheckTypeArguments(namedType.TypeArguments, operation, context, platformSpecificOperations, platformSpecificMembers, msBuildPlatforms);
CheckTypeArguments(namedType.TypeArguments);
}

static void CheckTypeArguments(ImmutableArray<ITypeSymbol> typeArguments, IOperation operation, OperationAnalysisContext context,
PooledConcurrentDictionary<KeyValuePair<IOperation, ISymbol>, (SmallDictionary<string, Versions> attributes,
SmallDictionary<string, Versions>? csAttributes)> platformSpecificOperations,
ConcurrentDictionary<ISymbol, PlatformAttributes> platformSpecificMembers, ImmutableArray<string> msBuildPlatforms)
void CheckTypeArguments(ImmutableArray<ITypeSymbol> typeArguments)
{
using var workingSet = PooledHashSet<ITypeSymbol>.GetInstance();
CheckTypeArgumentsCore(typeArguments, operation, context, platformSpecificOperations, platformSpecificMembers, msBuildPlatforms, workingSet);
CheckTypeArgumentsCore(typeArguments, workingSet);
}

static void CheckTypeArgumentsCore(ImmutableArray<ITypeSymbol> typeArguments, IOperation operation, OperationAnalysisContext context,
PooledConcurrentDictionary<KeyValuePair<IOperation, ISymbol>, (SmallDictionary<string, Versions> attributes,
SmallDictionary<string, Versions>? csAttributes)> platformSpecificOperations, ConcurrentDictionary<ISymbol,
PlatformAttributes> platformSpecificMembers, ImmutableArray<string> msBuildPlatforms, PooledHashSet<ITypeSymbol> workingSet)
void CheckTypeArgumentsCore(ImmutableArray<ITypeSymbol> typeArguments, PooledHashSet<ITypeSymbol> workingSet)
{
foreach (var typeArgument in typeArguments)
{
Expand All @@ -1070,33 +1076,32 @@ static void CheckTypeArgumentsCore(ImmutableArray<ITypeSymbol> typeArguments, IO
workingSet.Add(typeArgument);
if (typeArgument.SpecialType == SpecialType.None)
{
CheckOperationAttributes(operation, context, platformSpecificOperations, platformSpecificMembers, msBuildPlatforms, typeArgument, checkParents: true);
CheckOperationAttributes(typeArgument, checkParents: true);

if (typeArgument is INamedTypeSymbol nType && nType.IsGenericType)
{
CheckTypeArgumentsCore(nType.TypeArguments, operation, context, platformSpecificOperations, platformSpecificMembers, msBuildPlatforms, workingSet);
CheckTypeArgumentsCore(nType.TypeArguments, workingSet);
}
}
}
}
}

static void CheckOperationAttributes(IOperation operation, OperationAnalysisContext context, PooledConcurrentDictionary<KeyValuePair<IOperation, ISymbol>,
(SmallDictionary<string, Versions> attributes, SmallDictionary<string, Versions>? csAttributes)> platformSpecificOperations,
ConcurrentDictionary<ISymbol, PlatformAttributes> platformSpecificMembers, ImmutableArray<string> msBuildPlatforms, ISymbol symbol, bool checkParents)
void CheckOperationAttributes(ISymbol symbol, bool checkParents)
{
if (TryGetOrCreatePlatformAttributes(symbol, checkParents, platformSpecificMembers, out var operationAttributes))
if (TryGetOrCreatePlatformAttributes(symbol, checkParents, crossPlatform, platformSpecificMembers, out var operationAttributes))
{
var containingSymbol = context.ContainingSymbol;
if (containingSymbol is IMethodSymbol method && method.IsAccessorMethod())
{
containingSymbol = method.AssociatedSymbol;
}

if (TryGetOrCreatePlatformAttributes(containingSymbol, true, platformSpecificMembers, out var callSiteAttributes))
if (TryGetOrCreatePlatformAttributes(containingSymbol, true, crossPlatform, platformSpecificMembers, out var callSiteAttributes))
{
if (callSiteAttributes.Callsite != Callsite.Empty &&
IsNotSuppressedByCallSite(operationAttributes.Platforms!, callSiteAttributes.Platforms!, msBuildPlatforms, out var notSuppressedAttributes))
IsNotSuppressedByCallSite(operationAttributes.Platforms!, callSiteAttributes.Platforms!, msBuildPlatforms,
out var notSuppressedAttributes, crossPlatform & operationAttributes.IsAssemblyAttribute))
{
platformSpecificOperations.TryAdd(new KeyValuePair<IOperation, ISymbol>(operation, symbol), (notSuppressedAttributes, callSiteAttributes.Platforms));
}
Expand Down Expand Up @@ -1144,7 +1149,7 @@ private static PlatformAttributes CopyAttributes(PlatformAttributes copyAttribut
{
copy.Platforms!.Add(platformName, CopyAllAttributes(new Versions(), attributes));
}

copy.IsAssemblyAttribute = copyAttributes.IsAssemblyAttribute;
return copy;
}

Expand Down Expand Up @@ -1181,7 +1186,7 @@ private static SmallDictionary<string, Versions> CopyAttributes(SmallDictionary<

private static bool IsNotSuppressedByCallSite(SmallDictionary<string, Versions> operationAttributes,
SmallDictionary<string, Versions> callSiteAttributes, ImmutableArray<string> msBuildPlatforms,
out SmallDictionary<string, Versions> notSuppressedAttributes)
out SmallDictionary<string, Versions> notSuppressedAttributes, bool crossPlatform)
{
notSuppressedAttributes = new SmallDictionary<string, Versions>(StringComparer.OrdinalIgnoreCase);
bool? mandatorySupportFound = null;
Expand Down Expand Up @@ -1322,17 +1327,19 @@ private static bool IsNotSuppressedByCallSite(SmallDictionary<string, Versions>
}
}
}

// if supportedOnlyList then call site should not have any platform not listed in the support list
foreach (var (platform, csAttributes) in callSiteAttributes)
else if (!crossPlatform)
{
if (csAttributes.SupportedFirst != null &&
!supportedOnlyPlatforms.Contains(platform) &&
!notSuppressedAttributes.ContainsKey(platform))
// if supportedOnlyList then call site should not have any platform not listed in the support list
foreach (var (platform, csAttributes) in callSiteAttributes)
{
foreach (var (name, version) in operationAttributes)
if (csAttributes.SupportedFirst != null &&
!supportedOnlyPlatforms.Contains(platform) &&
!notSuppressedAttributes.ContainsKey(platform))
{
AddOrUpdatedDiagnostic(operationAttributes[name], notSuppressedAttributes, name);
foreach (var (name, version) in operationAttributes)
{
AddOrUpdatedDiagnostic(operationAttributes[name], notSuppressedAttributes, name);
}
}
}
}
Expand Down Expand Up @@ -1401,7 +1408,7 @@ pOperation.Parent is IBinaryOperation bo &&
bo.OperatorKind == BinaryOperatorKind.LessThanOrEqual);

private static bool TryGetOrCreatePlatformAttributes(
ISymbol symbol, bool checkParents,
ISymbol symbol, bool checkParents, bool crossPlatform,
ConcurrentDictionary<ISymbol, PlatformAttributes> platformSpecificMembers,
out PlatformAttributes attributes)
{
Expand All @@ -1418,21 +1425,21 @@ private static bool TryGetOrCreatePlatformAttributes(
}

if (container != null &&
TryGetOrCreatePlatformAttributes(container, checkParents, platformSpecificMembers, out var containerAttributes))
TryGetOrCreatePlatformAttributes(container, checkParents, crossPlatform, platformSpecificMembers, out var containerAttributes))
{
attributes = CopyAttributes(containerAttributes);
}
}

attributes ??= new PlatformAttributes();
MergePlatformAttributes(symbol.GetAttributes(), ref attributes);
attributes ??= new PlatformAttributes() { IsAssemblyAttribute = symbol is IAssemblySymbol };
MergePlatformAttributes(symbol.GetAttributes(), ref attributes, crossPlatform);
attributes = platformSpecificMembers.GetOrAdd(symbol, attributes);
}

return attributes.Platforms != null;

static void MergePlatformAttributes(ImmutableArray<AttributeData> immediateAttributes,
ref PlatformAttributes parentAttributes)
ref PlatformAttributes parentAttributes, bool crossPlatform)
{
SmallDictionary<string, Versions>? childAttributes = null;
foreach (AttributeData attribute in immediateAttributes)
Expand Down Expand Up @@ -1546,6 +1553,18 @@ static void MergePlatformAttributes(ImmutableArray<AttributeData> immediateAttri
}
}

if (crossPlatform && parentAttributes.IsAssemblyAttribute)
{
foreach (var childAttribute in childAttributes)
{
if (AllowList(childAttribute.Value))
{
parentAttributes.Platforms = childAttributes;
break;
}
}
}

if (notFoundPlatforms.Count > 0)
{
// For allow list if child narrowing supported platforms by having less platforms support than parent,
Expand All @@ -1562,6 +1581,7 @@ static void MergePlatformAttributes(ImmutableArray<AttributeData> immediateAttri
parentAttributes.Callsite = Callsite.Empty;
}
}
parentAttributes.IsAssemblyAttribute = false;
}
else
{
Expand Down
Loading

0 comments on commit 5078028

Please sign in to comment.