diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatibilityAnalyzer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatibilityAnalyzer.cs index 33a9ad344b..183c6b7bbf 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatibilityAnalyzer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatibilityAnalyzer.cs @@ -172,10 +172,11 @@ public override void Initialize(AnalysisContext context) m.ReturnType.Equals(osPlatformType) && m.Parameters.Length == 1 && m.Parameters[0].Type.SpecialType == SpecialType.System_String).FirstOrDefault(); + var notSupportedExceptionType = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemNotSupportedException); context.RegisterOperationBlockStartAction( context => AnalyzeOperationBlock(context, guardMethods, runtimeIsOSPlatformMethod, osPlatformCreateMethod, - osPlatformTypeArray, stringType, platformSpecificMembers, msBuildPlatforms)); + osPlatformTypeArray, stringType, platformSpecificMembers, msBuildPlatforms, notSupportedExceptionType)); }); static ImmutableArray GetOperatingSystemGuardMethods(IMethodSymbol? runtimeIsOSPlatformMethod, INamedTypeSymbol operatingSystemType) @@ -230,15 +231,21 @@ private void AnalyzeOperationBlock( ImmutableArray osPlatformTypeArray, INamedTypeSymbol stringType, ConcurrentDictionary?> platformSpecificMembers, - ImmutableArray msBuildPlatforms) + ImmutableArray msBuildPlatforms, + ITypeSymbol? notSupportedExceptionType) { + if (context.IsMethodNotImplementedOrSupported(checkPlatformNotSupported: true)) + { + return; + } + var osPlatformType = osPlatformTypeArray[0]; var platformSpecificOperations = PooledConcurrentDictionary attributes, SmallDictionary? csAttributes)>.GetInstance(); context.RegisterOperationAction(context => { - AnalyzeOperation(context.Operation, context, platformSpecificOperations, platformSpecificMembers, msBuildPlatforms); + AnalyzeOperation(context.Operation, context, platformSpecificOperations, platformSpecificMembers, msBuildPlatforms, notSupportedExceptionType); }, OperationKind.MethodReference, OperationKind.EventReference, @@ -977,8 +984,14 @@ private static ISymbol GetEventAccessor(IEventSymbol iEvent, IOperation operatio private static void AnalyzeOperation(IOperation operation, OperationAnalysisContext context, PooledConcurrentDictionary attributes, SmallDictionary? csAttributes)> platformSpecificOperations, - ConcurrentDictionary?> platformSpecificMembers, ImmutableArray msBuildPlatforms) + ConcurrentDictionary?> platformSpecificMembers, ImmutableArray msBuildPlatforms, + ITypeSymbol? notSupportedExceptionType) { + if (operation.Parent is IArgumentOperation argumentOperation && UsedInCreatingNotSupportedException(argumentOperation, notSupportedExceptionType)) + { + return; + } + var symbol = GetOperationSymbol(operation); if (symbol == null) @@ -1038,6 +1051,18 @@ static void CheckOperationAttributes(IOperation operation, OperationAnalysisCont } } + private static bool UsedInCreatingNotSupportedException(IArgumentOperation operation, ITypeSymbol? notSupportedExceptionType) + { + if (operation.Parent is IObjectCreationOperation creation && + operation.Parameter.Type.SpecialType == SpecialType.System_String && + creation.Type.DerivesFrom(notSupportedExceptionType, baseTypesOnly: true, checkTypeParameterConstraints: false)) + { + return true; + } + + return false; + } + private static bool TryCopyAttributesNotSuppressedByMsBuild(SmallDictionary operationAttributes, ImmutableArray msBuildPlatforms, out SmallDictionary copiedAttributes) { diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatibilityAnalyzerTests.GuardedCallsTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatibilityAnalyzerTests.GuardedCallsTests.cs index 37b3a4cea3..15731faeaa 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatibilityAnalyzerTests.GuardedCallsTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatibilityAnalyzerTests.GuardedCallsTests.cs @@ -1552,7 +1552,7 @@ public void M2() await VerifyAnalyzerAsyncCs(source); } - [Fact(Skip = "TODO: Failing because we cannot detect the correct local invocation being called")] + [Fact] public async Task LocalFunctionCallsPlatformDependentMember_InvokedFromDifferentContext() { var source = @" @@ -1562,24 +1562,17 @@ public class Test { void M() { + LocalM(); if (OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 10, 2)) { - LocalM(true); + LocalM(); } - LocalM(false); return; - void LocalM(bool suppressed) + void LocalM() { - if (suppressed) - { - WindowsOnlyMethod(); - } - else - { - [|WindowsOnlyMethod()|]; - } - + [|WindowsOnlyMethod()|]; + if (OperatingSystemHelper.IsOSPlatformVersionAtLeast(""Windows"", 10, 2)) { WindowsOnlyMethod(); @@ -1595,6 +1588,7 @@ void LocalM(bool suppressed) } } } + [SupportedOSPlatform(""Windows10.1.2.3"")] public void WindowsOnlyMethod() { @@ -1605,7 +1599,7 @@ public void UnsupportedWindows10() } } " + MockAttributesCsSource + MockOperatingSystemApiSource; - await VerifyAnalyzerAsyncCs(source); + await VerifyAnalyzerAsyncCs(source, s_msBuildPlatforms); } [Fact] diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatibilityAnalyzerTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatibilityAnalyzerTests.cs index 7f04298186..0171bb50f5 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatibilityAnalyzerTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/InteropServices/PlatformCompatibilityAnalyzerTests.cs @@ -126,11 +126,212 @@ class Target [SupportedOSPlatform(""windows"")] public static void WindowsOnlyMethod() { } } -} -" + MockAttributesCsSource; +}" + MockAttributesCsSource; await VerifyAnalyzerAsyncCs(source, tfmAndOption); } + [Fact] + public async Task OnlyThrowsNotSupportedWithOsDependentStringNotWarns() + { + var csSource = @" +using System; +using System.Runtime.Versioning; + +[SupportedOSPlatform(""Browser"")] +public class Test +{ + private static string s_message = ""Browser not supported""; + + [UnsupportedOSPlatform(""browser"")] + public static void ThrowPnseWithStringArgument() { throw new PlatformNotSupportedException(s_message); } + + [UnsupportedOSPlatform(""browser"")] + public static void ThrowNotSupportedWithStringArgument() { throw new NotSupportedException(s_message); } + + [UnsupportedOSPlatform(""browser"")] + public static void ThrowPnseWithStringAndExceptionArgument() { throw new PlatformNotSupportedException(s_message, new Exception(s_message)); } + + [UnsupportedOSPlatform(""browser"")] + public static void ThrowPnseDefaultConstructor() { throw new PlatformNotSupportedException(); } +}" + MockAttributesCsSource; + await VerifyAnalyzerAsyncCs(csSource); + } + + [Fact] + public async Task ThrowNotSupportedWithOtherOsDependentApiUsageNotWarns() + { + var csSource = @" +using System; +using System.Runtime.Versioning; +[assembly: SupportedOSPlatform(""browser"")] + +public static class SR +{ + public static string Message {get; set;} +} + +[UnsupportedOSPlatform(""browser"")] +public class Test +{ + void ThrowWithStringArgument() + { + [|SR.Message|] = ""Warns not reachable on Browser""; + throw new PlatformNotSupportedException(SR.Message); + } + + void ThrowNotSupportedWithStringArgument() + { + [|SR.Message|] = ""Warns not reachable on Browser""; + throw new NotSupportedException(SR.Message); + } + + void ThrowWithNoArgument() + { + [|SR.Message|] = ""Warns not reachable on Browser""; + throw new PlatformNotSupportedException(); + } + + void ThrowWithStringAndExceptionConstructor() + { + [|SR.Message|] = ""Warns not reachable on Browser""; + throw new PlatformNotSupportedException(SR.Message, new Exception()); + } + + void ThrowWithAnotherExceptionUsingResourceString() + { + [|SR.Message|] = ""Warns not reachable on Browser""; + throw new PlatformNotSupportedException(SR.Message, new Exception([|SR.Message|])); + } +}" + MockAttributesCsSource; + await VerifyAnalyzerAsyncCs(csSource); + } + + [Fact] + public async Task ThrowNotSupportedWithOtherStatementAndWithinConditionNotWarns() + { + var csSource = @" +using System; +using System.Runtime.Versioning; + +[SupportedOSPlatform(""windows"")] +public static class Windows +{ + public static string Message {get; set;} +} + +[SupportedOSPlatform(""browser"")] +public static class SR +{ + public static string Message {get; set;} + public static void M1() { } +} + +public class Test +{ + void ThrowWithStringConstructor() + { + [|SR.M1()|]; + if (!OperatingSystemHelper.IsBrowser()) + { + throw new PlatformNotSupportedException(SR.Message); + } + SR.M1(); + + [|Windows.Message|] = ""Warns supported only on Windows""; + if (!OperatingSystemHelper.IsWindows()) + { + throw new NotSupportedException(Windows.Message); + } + Windows.Message = ""It is windows!""; + } + + void ThrowWithOtherConstructorWarnsForInnnerException() + { + [|SR.M1()|]; + if (!OperatingSystemHelper.IsBrowser()) + { + throw new PlatformNotSupportedException(); + } + SR.M1(); + + [|Windows.Message|] = ""Warns supported only on Windows""; + if (!OperatingSystemHelper.IsWindows()) + { + throw new NotSupportedException(Windows.Message, new Exception([|Windows.Message|])); + } + } +}" + MockAttributesCsSource + MockOperatingSystemApiSource; + await VerifyAnalyzerAsyncCs(csSource); + } + + [Fact] + public async Task CreateNotSupportedWithOsDependentStringNotWarns() + { + var csSource = @" +using System; +using System.Runtime.Versioning; + +[SupportedOSPlatform(""Browser"")] +public class Test +{ + private static string s_message = ""Browser not supported""; + + [UnsupportedOSPlatform(""browser"")] + public static Exception GetPnseWithStringArgument() { return new PlatformNotSupportedException(s_message); } + + [UnsupportedOSPlatform(""browser"")] + public static Exception GetNotSupportedWithStringArgument() + { + [|s_message|] = ""Warns not reachable on Browser""; + return new NotSupportedException(s_message); + } + + [UnsupportedOSPlatform(""browser"")] + public static Exception ThrowPnseWithStringWarnsForInnerException() + { + return new PlatformNotSupportedException(s_message, new Exception([|s_message|])); + } + + [UnsupportedOSPlatform(""browser"")] + public static Exception ThrowPnseDefaultConstructor() { return new PlatformNotSupportedException(); } +}" + MockAttributesCsSource; + await VerifyAnalyzerAsyncCs(csSource); + } + + [Fact] + public async Task ThrowNotSupportedWarnsForNonStringArgument() + { + var csSource = @" +using System; +using System.Runtime.Versioning; + +[SupportedOSPlatform(""windows"")] +public class WindowsOnlyException : Exception +{ + public WindowsOnlyException() { } + public WindowsOnlyException(string message) { } + public static string Message {get; set;} +} + +public class Test +{ + void ThrowWindowsOnlyExceptionWarns() + { + [|WindowsOnlyException.Message|] = ""Warns for message and exception""; + throw [|new WindowsOnlyException([|WindowsOnlyException.Message|])|]; + } + + void ThrowWithWindowsOnlyInnnerExceptionWarns() + { + if (!OperatingSystemHelper.IsWindows()) + { + throw new NotSupportedException(WindowsOnlyException.Message, [|new WindowsOnlyException()|]); + } + } +}" + MockAttributesCsSource + MockOperatingSystemApiSource; + await VerifyAnalyzerAsyncCs(csSource); + } + [Fact] public async Task OsDependentMethodsCalledWarns() { diff --git a/src/Utilities/Compiler/Extensions/OperationBlockAnalysisContextExtension.cs b/src/Utilities/Compiler/Extensions/OperationBlockAnalysisContextExtension.cs index ccd3351747..0803aad909 100644 --- a/src/Utilities/Compiler/Extensions/OperationBlockAnalysisContextExtension.cs +++ b/src/Utilities/Compiler/Extensions/OperationBlockAnalysisContextExtension.cs @@ -11,7 +11,7 @@ namespace Analyzer.Utilities.Extensions internal static class OperationBlockAnalysisContextExtension { #pragma warning disable RS1012 // Start action has no registered actions. - public static bool IsMethodNotImplementedOrSupported(this OperationBlockStartAnalysisContext context) + public static bool IsMethodNotImplementedOrSupported(this OperationBlockStartAnalysisContext context, bool checkPlatformNotSupported = false) #pragma warning restore RS1012 // Start action has no registered actions. { // Note that VB method bodies with 1 action have 3 operations. @@ -53,8 +53,10 @@ body.Operations[1] is ILabeledOperation labeledOp && labeledOp.IsImplicit && descendants[0] is IThrowOperation throwOperation && throwOperation.GetThrownExceptionType() is ITypeSymbol createdExceptionType) { - if (Equals(context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemNotImplementedException), createdExceptionType.OriginalDefinition) - || Equals(context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemNotSupportedException), createdExceptionType.OriginalDefinition)) + if (Equals(context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemNotImplementedException), createdExceptionType.OriginalDefinition) || + Equals(context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemNotSupportedException), createdExceptionType.OriginalDefinition) || + (checkPlatformNotSupported && + Equals(context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemPlatformNotSupportedException), createdExceptionType.OriginalDefinition))) { return true; } diff --git a/src/Utilities/Compiler/WellKnownTypeNames.cs b/src/Utilities/Compiler/WellKnownTypeNames.cs index 4735731e48..11f2317043 100644 --- a/src/Utilities/Compiler/WellKnownTypeNames.cs +++ b/src/Utilities/Compiler/WellKnownTypeNames.cs @@ -224,6 +224,7 @@ internal static class WellKnownTypeNames public const string SystemObsoleteAttribute = "System.ObsoleteAttribute"; public const string SystemOperatingSystem = "System.OperatingSystem"; public const string SystemOutOfMemoryException = "System.OutOfMemoryException"; + public const string SystemPlatformNotSupportedException = "System.PlatformNotSupportedException"; public const string SystemRandom = "System.Random"; public const string SystemRange = "System.Range"; public const string SystemReadOnlyMemory1 = "System.ReadOnlyMemory`1";