From cc63319000132eeb68f91f12fe1daf7a53fe168a Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Mon, 23 Oct 2023 20:48:33 -0600 Subject: [PATCH] Fix VSTHRD012 false positive due to inaccessible overloads Fixes #1111 --- ...AbstractVSTHRD012SpecifyJtfWhereAllowed.cs | 6 +- .../Utils.cs | 16 ++++++ .../VSTHRD012SpecifyJtfWhereAllowedTests.cs | 57 +++++++++++++++++++ 3 files changed, 77 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.VisualStudio.Threading.Analyzers/AbstractVSTHRD012SpecifyJtfWhereAllowed.cs b/src/Microsoft.VisualStudio.Threading.Analyzers/AbstractVSTHRD012SpecifyJtfWhereAllowed.cs index 338f1f5b5..969e32176 100644 --- a/src/Microsoft.VisualStudio.Threading.Analyzers/AbstractVSTHRD012SpecifyJtfWhereAllowed.cs +++ b/src/Microsoft.VisualStudio.Threading.Analyzers/AbstractVSTHRD012SpecifyJtfWhereAllowed.cs @@ -81,8 +81,10 @@ private static void AnalyzeCall(OperationAnalysisContext context, Location locat // The method being invoked doesn't take any JTC/JTF parameters. // Look for an overload that does. bool preferableAlternativesExist = otherOverloads - .Where(m => !m.IsObsolete()) - .Any(m => m.Parameters.Skip(m.IsExtensionMethod ? 1 : 0).Any(IsImportantJtfParameter)); + .Any(m => + !m.IsObsolete() && + m.Parameters.Skip(m.IsExtensionMethod ? 1 : 0).Any(IsImportantJtfParameter) && + context.ContainingSymbol.FindContainingNamedOrAssemblySymbol() is ISymbol containingSymbol && context.Compilation.IsSymbolAccessibleWithin(m, containingSymbol)); if (preferableAlternativesExist) { Diagnostic diagnostic = Diagnostic.Create( diff --git a/src/Microsoft.VisualStudio.Threading.Analyzers/Utils.cs b/src/Microsoft.VisualStudio.Threading.Analyzers/Utils.cs index 7ef775888..77d0cc712 100644 --- a/src/Microsoft.VisualStudio.Threading.Analyzers/Utils.cs +++ b/src/Microsoft.VisualStudio.Threading.Analyzers/Utils.cs @@ -700,6 +700,22 @@ internal static IOperation FindFinalAncestor(IOperation operation) return default; } + internal static ISymbol? FindContainingNamedOrAssemblySymbol(this ISymbol? symbol) + { + ISymbol? candidate = symbol; + while (candidate is not null) + { + if (candidate is INamedTypeSymbol or IAssemblySymbol) + { + return candidate; + } + + candidate = candidate.ContainingSymbol; + } + + return null; + } + private static bool IsSymbolTheRightType(ISymbol symbol, string typeName, IReadOnlyList namespaces) { var fieldSymbol = symbol as IFieldSymbol; diff --git a/test/Microsoft.VisualStudio.Threading.Analyzers.Tests/VSTHRD012SpecifyJtfWhereAllowedTests.cs b/test/Microsoft.VisualStudio.Threading.Analyzers.Tests/VSTHRD012SpecifyJtfWhereAllowedTests.cs index dce0000a1..a9cfa155a 100644 --- a/test/Microsoft.VisualStudio.Threading.Analyzers.Tests/VSTHRD012SpecifyJtfWhereAllowedTests.cs +++ b/test/Microsoft.VisualStudio.Threading.Analyzers.Tests/VSTHRD012SpecifyJtfWhereAllowedTests.cs @@ -254,4 +254,61 @@ void F() { await CSVerify.VerifyAnalyzerAsync(test); } + + [Fact] + public async Task InaccessibleMembers_Private_GeneratesNoWarning() + { + string test = """ + using System; + using Microsoft.VisualStudio.Threading; + + static class Extensions + { + public static void OnMainThread(Action action) => OnMainThread(null, action); + + private static void OnMainThread(JoinableTaskFactory factory, Action action) => factory.Run(async delegate + { + await factory.SwitchToMainThreadAsync(); + action(); + }); + } + + class Foo + { + void Bar() + { + Extensions.OnMainThread(() => { }); + } + } + """; + + await CSVerify.VerifyAnalyzerAsync(test); + } + + [Fact] + public async Task AccessibleMembers_Private_GeneratesWarning() + { + string test = """ + using System; + using Microsoft.VisualStudio.Threading; + + static class Extensions + { + public static void OnMainThread(Action action) => OnMainThread(null, action); + + private static void OnMainThread(JoinableTaskFactory factory, Action action) => factory.Run(async delegate + { + await factory.SwitchToMainThreadAsync(); + action(); + }); + + static void Bar() + { + [|OnMainThread|](() => { }); + } + } + """; + + await CSVerify.VerifyAnalyzerAsync(test); + } }