From 0fd1fa12f2d20b446b307ddcb2045f256042f0b5 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 21 May 2021 11:36:55 -0700 Subject: [PATCH] Require that methods marked with GeneratedDllImport are in types that are marked partial and any parents of their containing types are also marked partial. (dotnet/runtimelab#1091) Commit migrated from https://github.com/dotnet/runtimelab/commit/53af4b5dfa7e4d77b34151523e82665974da329c --- .../AnalyzerReleases.Unshipped.md | 1 + .../Analyzers/AnalyzerDiagnostics.cs | 1 + .../Analyzers/GeneratedDllImportAnalyzer.cs | 32 +++++++++--- .../DllImportGenerator/DllImportGenerator.cs | 9 ++++ .../DllImportGenerator/Resources.Designer.cs | 27 ++++++++++ .../gen/DllImportGenerator/Resources.resx | 9 ++++ .../GeneratedDllImportAnalyzerTests.cs | 50 +++++++++++++++++++ 7 files changed, 123 insertions(+), 6 deletions(-) diff --git a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/AnalyzerReleases.Unshipped.md b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/AnalyzerReleases.Unshipped.md index 2e88786cee8fa..87ac29a463b2f 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/AnalyzerReleases.Unshipped.md +++ b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/AnalyzerReleases.Unshipped.md @@ -21,3 +21,4 @@ DLLIMPORTGENANALYZER013 | Usage | Warning | GeneratedDllImportMissin DLLIMPORTGENANALYZER014 | Usage | Error | RefValuePropertyUnsupported DLLIMPORTGENANALYZER015 | Interoperability | Disabled | ConvertToGeneratedDllImportAnalyzer DLLIMPORTGENANALYZER016 | Usage | Error | GenericTypeMustBeClosed +DLLIMPORTGENANALYZER017 | Usage | Warning | GeneratedDllImportContainingTypeMissingModifiers diff --git a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Analyzers/AnalyzerDiagnostics.cs b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Analyzers/AnalyzerDiagnostics.cs index 17a03e0447524..88a5f269f322d 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Analyzers/AnalyzerDiagnostics.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Analyzers/AnalyzerDiagnostics.cs @@ -29,6 +29,7 @@ public static class Ids // GeneratedDllImport public const string GeneratedDllImportMissingRequiredModifiers = Prefix + "013"; + public const string GeneratedDllImportContaiingTypeMissingRequiredModifiers = Prefix + "017"; // Migration from DllImport to GeneratedDllImport public const string ConvertToGeneratedDllImport = Prefix + "015"; diff --git a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Analyzers/GeneratedDllImportAnalyzer.cs b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Analyzers/GeneratedDllImportAnalyzer.cs index 3bedc018cf583..a0a0ceb3c67fd 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Analyzers/GeneratedDllImportAnalyzer.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Analyzers/GeneratedDllImportAnalyzer.cs @@ -25,7 +25,17 @@ public class GeneratedDllImportAnalyzer : DiagnosticAnalyzer isEnabledByDefault: true, description: GetResourceString(nameof(Resources.GeneratedDllImportMissingModifiersDescription))); - public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(GeneratedDllImportMissingModifiers); + public readonly static DiagnosticDescriptor GeneratedDllImportContainingTypeMissingModifiers = + new DiagnosticDescriptor( + Ids.GeneratedDllImportContaiingTypeMissingRequiredModifiers, + GetResourceString(nameof(Resources.GeneratedDllImportContainingTypeMissingModifiersTitle)), + GetResourceString(nameof(Resources.GeneratedDllImportContainingTypeMissingModifiersMessage)), + Category, + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: GetResourceString(nameof(Resources.GeneratedDllImportContainingTypeMissingModifiersDescription))); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(GeneratedDllImportMissingModifiers, GeneratedDllImportContainingTypeMissingModifiers); public override void Initialize(AnalysisContext context) { @@ -64,17 +74,27 @@ private static void AnalyzeSymbol(SymbolAnalysisContext context, INamedTypeSymbo foreach (var reference in methodSymbol.DeclaringSyntaxReferences) { var syntax = reference.GetSyntax(context.CancellationToken); - var methodSyntax = syntax as MethodDeclarationSyntax; - if (methodSyntax == null) - continue; - - if (!methodSyntax.Modifiers.Any(SyntaxKind.PartialKeyword)) + if (syntax is MethodDeclarationSyntax methodSyntax && !methodSyntax.Modifiers.Any(SyntaxKind.PartialKeyword)) { // Must be marked partial context.ReportDiagnostic(methodSymbol.CreateDiagnostic(GeneratedDllImportMissingModifiers, methodSymbol.Name)); break; } } + + for (INamedTypeSymbol? typeSymbol = methodSymbol.ContainingType; typeSymbol is not null; typeSymbol = typeSymbol.ContainingType) + { + foreach (var reference in typeSymbol.DeclaringSyntaxReferences) + { + var syntax = reference.GetSyntax(context.CancellationToken); + if (syntax is TypeDeclarationSyntax typeSyntax && !typeSyntax.Modifiers.Any(SyntaxKind.PartialKeyword)) + { + // Must be marked partial + context.ReportDiagnostic(typeSymbol.CreateDiagnostic(GeneratedDllImportContainingTypeMissingModifiers, typeSymbol.Name)); + break; + } + } + } } } } diff --git a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/DllImportGenerator.cs b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/DllImportGenerator.cs index f0a33362b3eb5..de2038f7a0979 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/DllImportGenerator.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/DllImportGenerator.cs @@ -285,6 +285,15 @@ public void OnVisitSyntaxNode(GeneratorSyntaxContext context) return; } + // Verify that the types the method is declared in are marked partial. + for (SyntaxNode? parentNode = methodSyntax.Parent; parentNode is TypeDeclarationSyntax typeDecl; parentNode = parentNode.Parent) + { + if (!typeDecl.Modifiers.Any(SyntaxKind.PartialKeyword)) + { + return; + } + } + // Check if the method is marked with the GeneratedDllImport attribute. foreach (AttributeListSyntax listSyntax in methodSyntax.AttributeLists) { diff --git a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Resources.Designer.cs b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Resources.Designer.cs index 87fac1a3e0ffa..2acea21707cac 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Resources.Designer.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Resources.Designer.cs @@ -249,6 +249,33 @@ internal static string CustomTypeMarshallingNativeToManagedUnsupported { } } + /// + /// Looks up a localized string similar to Types that contain methods marked with 'GeneratedDllImportAttribute' must be 'partial'. P/Invoke source generation will ignore methods contained within non-partial types.. + /// + internal static string GeneratedDllImportContainingTypeMissingModifiersDescription { + get { + return ResourceManager.GetString("GeneratedDllImportContainingTypeMissingModifiersDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Type '{0}' contains methods marked with 'GeneratedDllImportAttribute' and should be 'partial'. P/Invoke source generation will ignore methods contained within non-partial types.. + /// + internal static string GeneratedDllImportContainingTypeMissingModifiersMessage { + get { + return ResourceManager.GetString("GeneratedDllImportContainingTypeMissingModifiersMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Types that contain methods marked with 'GeneratedDllImportAttribute' must be 'partial'.. + /// + internal static string GeneratedDllImportContainingTypeMissingModifiersTitle { + get { + return ResourceManager.GetString("GeneratedDllImportContainingTypeMissingModifiersTitle", resourceCulture); + } + } + /// /// Looks up a localized string similar to Methods marked with 'GeneratedDllImportAttribute' should be 'static' and 'partial'. P/Invoke source generation will ignore methods that are not 'static' and 'partial'.. /// diff --git a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Resources.resx b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Resources.resx index f66d2945ad7a8..698f32820bf24 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Resources.resx +++ b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Resources.resx @@ -181,6 +181,15 @@ The specified parameter needs to be marshalled from native to managed, but the native type '{0}' does not support it. + + Types that contain methods marked with 'GeneratedDllImportAttribute' must be 'partial'. P/Invoke source generation will ignore methods contained within non-partial types. + + + Type '{0}' contains methods marked with 'GeneratedDllImportAttribute' and should be 'partial'. P/Invoke source generation will ignore methods contained within non-partial types. + + + Types that contain methods marked with 'GeneratedDllImportAttribute' must be 'partial'. + Methods marked with 'GeneratedDllImportAttribute' should be 'static' and 'partial'. P/Invoke source generation will ignore methods that are not 'static' and 'partial'. diff --git a/src/libraries/System.Runtime.InteropServices/tests/DllImportGenerator.UnitTests/GeneratedDllImportAnalyzerTests.cs b/src/libraries/System.Runtime.InteropServices/tests/DllImportGenerator.UnitTests/GeneratedDllImportAnalyzerTests.cs index e05b24cf64fa9..63c5497902e20 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/DllImportGenerator.UnitTests/GeneratedDllImportAnalyzerTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/DllImportGenerator.UnitTests/GeneratedDllImportAnalyzerTests.cs @@ -139,5 +139,55 @@ partial class Test "; await VerifyCS.VerifyAnalyzerAsync(source); } + + [Theory] + [InlineData("class")] + [InlineData("struct")] + [InlineData("record")] + + public async Task NonPartialParentType_Diagnostic(string typeKind) + { + string source = $@" +using System.Runtime.InteropServices; +{typeKind} {{|#0:Test|}} +{{ + [GeneratedDllImport(""DoesNotExist"")] + static partial void {{|CS0751:Method2|}}(); +}} +"; + + await VerifyCS.VerifyAnalyzerAsync( + source, + VerifyCS.Diagnostic(GeneratedDllImportContainingTypeMissingModifiers) + .WithLocation(0) + .WithArguments("Test")); + } + + [Theory] + [InlineData("class")] + [InlineData("struct")] + [InlineData("record")] + + public async Task NonPartialGrandparentType_Diagnostic(string typeKind) + { + + string source = $@" +using System.Runtime.InteropServices; +{typeKind} {{|#0:Test|}} +{{ + partial class TestInner + {{ + [GeneratedDllImport(""DoesNotExist"")] + static partial void Method2(); + }} +}} +"; + + await VerifyCS.VerifyAnalyzerAsync( + source, + VerifyCS.Diagnostic(GeneratedDllImportContainingTypeMissingModifiers) + .WithLocation(0) + .WithArguments("Test")); + } } } \ No newline at end of file