Skip to content

Commit

Permalink
Create analyzer in ComInterfaceGenerator to enforce the generator is …
Browse files Browse the repository at this point in the history
…used with IUnknown only (dotnet#78189)

Creates an analyzer that warns if the GeneratedComInterfaceAttribute is
used with InterfaceTypeAttribute that has an argument that is not
'InterfaceIsIUnknown'
Co-authored-by: Jeremy Koritzinsky <[email protected]>
Co-authored-by: Elinor Fung <[email protected]>
  • Loading branch information
jtschuster authored Jan 12, 2023
1 parent 6e1ca64 commit 1630725
Show file tree
Hide file tree
Showing 26 changed files with 954 additions and 6 deletions.
10 changes: 10 additions & 0 deletions docs/project/list-of-diagnostics.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,16 @@ The diagnostic id values reserved for .NET Libraries analyzer warnings are `SYSL
| __`SYSLIB1078`__ | *_`SYSLIB1070`-`SYSLIB1089` reserved for System.Runtime.InteropServices.JavaScript.JSImportGenerator.* |
| __`SYSLIB1079`__ | *_`SYSLIB1070`-`SYSLIB1089` reserved for System.Runtime.InteropServices.JavaScript.JSImportGenerator.* |
| __`SYSLIB1080`__ | *_`SYSLIB1070`-`SYSLIB1089` reserved for System.Runtime.InteropServices.JavaScript.JSImportGenerator.* |
| __`SYSLIB1081`__ | *_`SYSLIB1070`-`SYSLIB1089` reserved for System.Runtime.InteropServices.JavaScript.JSImportGenerator.* |
| __`SYSLIB1082`__ | *_`SYSLIB1070`-`SYSLIB1089` reserved for System.Runtime.InteropServices.JavaScript.JSImportGenerator.* |
| __`SYSLIB1083`__ | *_`SYSLIB1070`-`SYSLIB1089` reserved for System.Runtime.InteropServices.JavaScript.JSImportGenerator.* |
| __`SYSLIB1084`__ | *_`SYSLIB1070`-`SYSLIB1089` reserved for System.Runtime.InteropServices.JavaScript.JSImportGenerator.* |
| __`SYSLIB1085`__ | *_`SYSLIB1070`-`SYSLIB1089` reserved for System.Runtime.InteropServices.JavaScript.JSImportGenerator.* |
| __`SYSLIB1086`__ | *_`SYSLIB1070`-`SYSLIB1089` reserved for System.Runtime.InteropServices.JavaScript.JSImportGenerator.* |
| __`SYSLIB1087`__ | *_`SYSLIB1070`-`SYSLIB1089` reserved for System.Runtime.InteropServices.JavaScript.JSImportGenerator.* |
| __`SYSLIB1088`__ | *_`SYSLIB1070`-`SYSLIB1089` reserved for System.Runtime.InteropServices.JavaScript.JSImportGenerator.* |
| __`SYSLIB1089`__ | *_`SYSLIB1070`-`SYSLIB1089` reserved for System.Runtime.InteropServices.JavaScript.JSImportGenerator.* |
| __`SYSLIB1090`__ | Invalid 'GeneratedComInterfaceAttribute' usage |


### Diagnostic Suppressions (`SYSLIBSUPPRESS****`)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using Microsoft.CodeAnalysis;
namespace Microsoft.Interop.Analyzers
{
public static class AnalyzerDiagnostics
{
public static class Ids
{
public const string Prefix = "SYSLIB";
public const string InvalidGeneratedComAttributeUsage = Prefix + "1090";
}

private const string Category = "ComInterfaceGenerator";

private static LocalizableResourceString GetResourceString(string resourceName)
{
return new LocalizableResourceString(resourceName, SR.ResourceManager, typeof(FxResources.Microsoft.Interop.ComInterfaceGenerator.SR));
}

public static readonly DiagnosticDescriptor InterfaceTypeNotSupported =
new DiagnosticDescriptor(
Ids.InvalidGeneratedComAttributeUsage,
GetResourceString(nameof(SR.InterfaceTypeNotSupportedTitle)),
GetResourceString(nameof(SR.InterfaceTypeNotSupportedMessage)),
Category,
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: GetResourceString(nameof(SR.InterfaceTypeNotSupportedMessage)));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;

namespace Microsoft.Interop.Analyzers
{
/// <summary>
/// Validates that if an interface has GeneratedComInterfaceAttribute and <see cref="InterfaceTypeAttribute"/>,
/// the <see cref="InterfaceTypeAttribute"/> is given a <see cref="ComInterfaceType"/> that is supported by the generator.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class GeneratedComInterfaceAttributeAnalyzer : DiagnosticAnalyzer
{
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; }
= ImmutableArray.Create(AnalyzerDiagnostics.InterfaceTypeNotSupported);

public static readonly ImmutableArray<ComInterfaceType> SupportedComInterfaceTypes = ImmutableArray.Create(ComInterfaceType.InterfaceIsIUnknown);

public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterSymbolAction((context) =>
{
INamedTypeSymbol typeSymbol = (INamedTypeSymbol)context.Symbol;
if (typeSymbol.TypeKind != TypeKind.Interface)
return;

ImmutableArray<AttributeData> customAttributes = typeSymbol.GetAttributes();
if (customAttributes.Length == 0)
return;

// Interfaces with both GeneratedComInterfaceAttribute and InterfaceTypeAttribute should only have [InterfaceTypeAttribute(InterfaceIsIUnknown)]
if (GetAttribute(typeSymbol, TypeNames.GeneratedComInterfaceAttribute, out _)
&& GetAttribute(typeSymbol, TypeNames.InterfaceTypeAttribute, out AttributeData? comInterfaceAttribute)
&& !InterfaceTypeAttributeIsSupported(comInterfaceAttribute, out string unsupportedValue))
{
context.ReportDiagnostic(comInterfaceAttribute.CreateDiagnostic(AnalyzerDiagnostics.InterfaceTypeNotSupported, unsupportedValue));
}
}, SymbolKind.NamedType);
}

private static bool InterfaceTypeAttributeIsSupported(AttributeData comInterfaceAttribute, out string argument)
{
if (comInterfaceAttribute.ConstructorArguments.IsEmpty)
{
argument = "<empty>";
return false;
}
TypedConstant ctorArg0 = comInterfaceAttribute.ConstructorArguments[0];
ComInterfaceType interfaceType;

argument = ctorArg0.ToCSharpString();
switch (ctorArg0.Type.ToDisplayString())
{
case TypeNames.ComInterfaceTypeAttribute:
interfaceType = (ComInterfaceType)ctorArg0.Value;
break;
case TypeNames.System_Int16:
case TypeNames.@short:
interfaceType = (ComInterfaceType)(short)ctorArg0.Value;
break;
default:
return false;
}

return SupportedComInterfaceTypes.Contains(interfaceType);
}

private static bool GetAttribute(ISymbol symbol, string attributeDisplayName, [NotNullWhen(true)] out AttributeData? attribute)
{
foreach (AttributeData attr in symbol.GetAttributes())
{
if (attr.AttributeClass?.ToDisplayString() == attributeDisplayName)
{
attribute = attr;
return true;
}
}

attribute = null;
return false;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.CodeAnalysis;

namespace Microsoft.Interop
{
Expand Down Expand Up @@ -155,6 +154,7 @@ public class Ids
isEnabledByDefault: true,
description: GetResourceString(nameof(SR.ConfigurationNotSupportedDescription)));


private readonly List<Diagnostic> _diagnostics = new List<Diagnostic>();

public IEnumerable<Diagnostic> Diagnostics => _diagnostics;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,4 +201,10 @@
<data name="InvalidExceptionMarshallingValue" xml:space="preserve">
<value>The provided value is not a known flag of the 'ExceptionMarshalling' enum.</value>
</data>
<data name="InterfaceTypeNotSupportedTitle" xml:space="preserve">
<value>'GeneratedComInterfaceType' does not support the 'ComInterfaceType' value supplied to 'InterfaceTypeAttribute' on the same type.</value>
</data>
<data name="InterfaceTypeNotSupportedMessage" xml:space="preserve">
<value>Using 'GeneratedComInterfaceAttribute' and 'InterfaceTypeAttribute' is not supported with 'ComInterfaceType' value '{0}'.</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,16 @@
<target state="translated">Určenou konfiguraci nepodporují zdrojem generovaná volání P/Invokes.</target>
<note />
</trans-unit>
<trans-unit id="InterfaceTypeNotSupportedMessage">
<source>Using 'GeneratedComInterfaceAttribute' and 'InterfaceTypeAttribute' is not supported with 'ComInterfaceType' value '{0}'.</source>
<target state="new">Using 'GeneratedComInterfaceAttribute' and 'InterfaceTypeAttribute' is not supported with 'ComInterfaceType' value '{0}'.</target>
<note />
</trans-unit>
<trans-unit id="InterfaceTypeNotSupportedTitle">
<source>'GeneratedComInterfaceType' does not support the 'ComInterfaceType' value supplied to 'InterfaceTypeAttribute' on the same type.</source>
<target state="new">'GeneratedComInterfaceType' does not support the 'ComInterfaceType' value supplied to 'InterfaceTypeAttribute' on the same type.</target>
<note />
</trans-unit>
<trans-unit id="InvalidAttributedMethodContainingTypeMissingModifiersMessage">
<source>Method '{0}' is contained in a type '{1}' that is not marked 'partial'. P/Invoke source generation will ignore method '{0}'.</source>
<target state="translated">Metoda {0} je obsažena v typu {1}, který není označen jako „partial“. Generování zdrojů volání P/Invoke bude metodu {0} ignorovat.</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,16 @@
<target state="translated">Die angegebene Konfiguration wird von quellgenerierten P/Invokes nicht unterstützt.</target>
<note />
</trans-unit>
<trans-unit id="InterfaceTypeNotSupportedMessage">
<source>Using 'GeneratedComInterfaceAttribute' and 'InterfaceTypeAttribute' is not supported with 'ComInterfaceType' value '{0}'.</source>
<target state="new">Using 'GeneratedComInterfaceAttribute' and 'InterfaceTypeAttribute' is not supported with 'ComInterfaceType' value '{0}'.</target>
<note />
</trans-unit>
<trans-unit id="InterfaceTypeNotSupportedTitle">
<source>'GeneratedComInterfaceType' does not support the 'ComInterfaceType' value supplied to 'InterfaceTypeAttribute' on the same type.</source>
<target state="new">'GeneratedComInterfaceType' does not support the 'ComInterfaceType' value supplied to 'InterfaceTypeAttribute' on the same type.</target>
<note />
</trans-unit>
<trans-unit id="InvalidAttributedMethodContainingTypeMissingModifiersMessage">
<source>Method '{0}' is contained in a type '{1}' that is not marked 'partial'. P/Invoke source generation will ignore method '{0}'.</source>
<target state="translated">Die Methode \"{0}\" ist in einem Typ \"{1}\" enthalten, der nicht als \"partiell\" gekennzeichnet ist. Die P/Invoke-Quellgenerierung ignoriert die Methode \"{0}\".</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,16 @@
<target state="translated">La configuración especificada no está admitida por P/Invokes de un generador de código fuente.</target>
<note />
</trans-unit>
<trans-unit id="InterfaceTypeNotSupportedMessage">
<source>Using 'GeneratedComInterfaceAttribute' and 'InterfaceTypeAttribute' is not supported with 'ComInterfaceType' value '{0}'.</source>
<target state="new">Using 'GeneratedComInterfaceAttribute' and 'InterfaceTypeAttribute' is not supported with 'ComInterfaceType' value '{0}'.</target>
<note />
</trans-unit>
<trans-unit id="InterfaceTypeNotSupportedTitle">
<source>'GeneratedComInterfaceType' does not support the 'ComInterfaceType' value supplied to 'InterfaceTypeAttribute' on the same type.</source>
<target state="new">'GeneratedComInterfaceType' does not support the 'ComInterfaceType' value supplied to 'InterfaceTypeAttribute' on the same type.</target>
<note />
</trans-unit>
<trans-unit id="InvalidAttributedMethodContainingTypeMissingModifiersMessage">
<source>Method '{0}' is contained in a type '{1}' that is not marked 'partial'. P/Invoke source generation will ignore method '{0}'.</source>
<target state="translated">El método “{0}” está contenido en un tipo “{1}” que no está marcado como “partial”. La generación de código fuente P/Invoke omitirá el método “{0}”.</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,16 @@
<target state="translated">La configuration spécifiée n’est pas prise en charge par les P/Invokes générés par la source.</target>
<note />
</trans-unit>
<trans-unit id="InterfaceTypeNotSupportedMessage">
<source>Using 'GeneratedComInterfaceAttribute' and 'InterfaceTypeAttribute' is not supported with 'ComInterfaceType' value '{0}'.</source>
<target state="new">Using 'GeneratedComInterfaceAttribute' and 'InterfaceTypeAttribute' is not supported with 'ComInterfaceType' value '{0}'.</target>
<note />
</trans-unit>
<trans-unit id="InterfaceTypeNotSupportedTitle">
<source>'GeneratedComInterfaceType' does not support the 'ComInterfaceType' value supplied to 'InterfaceTypeAttribute' on the same type.</source>
<target state="new">'GeneratedComInterfaceType' does not support the 'ComInterfaceType' value supplied to 'InterfaceTypeAttribute' on the same type.</target>
<note />
</trans-unit>
<trans-unit id="InvalidAttributedMethodContainingTypeMissingModifiersMessage">
<source>Method '{0}' is contained in a type '{1}' that is not marked 'partial'. P/Invoke source generation will ignore method '{0}'.</source>
<target state="translated">La méthode « {0} » est contenue dans un type « {1} » qui n’est pas marqué comme étant « partial ». La génération source P/Invoke ignore la méthode « {0} ».</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,16 @@
<target state="translated">La configurazione specificata non è supportata dai P/Invoke generati dall'origine.</target>
<note />
</trans-unit>
<trans-unit id="InterfaceTypeNotSupportedMessage">
<source>Using 'GeneratedComInterfaceAttribute' and 'InterfaceTypeAttribute' is not supported with 'ComInterfaceType' value '{0}'.</source>
<target state="new">Using 'GeneratedComInterfaceAttribute' and 'InterfaceTypeAttribute' is not supported with 'ComInterfaceType' value '{0}'.</target>
<note />
</trans-unit>
<trans-unit id="InterfaceTypeNotSupportedTitle">
<source>'GeneratedComInterfaceType' does not support the 'ComInterfaceType' value supplied to 'InterfaceTypeAttribute' on the same type.</source>
<target state="new">'GeneratedComInterfaceType' does not support the 'ComInterfaceType' value supplied to 'InterfaceTypeAttribute' on the same type.</target>
<note />
</trans-unit>
<trans-unit id="InvalidAttributedMethodContainingTypeMissingModifiersMessage">
<source>Method '{0}' is contained in a type '{1}' that is not marked 'partial'. P/Invoke source generation will ignore method '{0}'.</source>
<target state="translated">Il metodo '{0}' è contenuto in un tipo '{1}' non contrassegnato come 'partial'. Durante la generazione dell'origine P/Invoke il metodo '{0}' verrà ignorato.</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,16 @@
<target state="translated">指定された構成は、ソースで生成された P/Invoke ではサポートされていません。</target>
<note />
</trans-unit>
<trans-unit id="InterfaceTypeNotSupportedMessage">
<source>Using 'GeneratedComInterfaceAttribute' and 'InterfaceTypeAttribute' is not supported with 'ComInterfaceType' value '{0}'.</source>
<target state="new">Using 'GeneratedComInterfaceAttribute' and 'InterfaceTypeAttribute' is not supported with 'ComInterfaceType' value '{0}'.</target>
<note />
</trans-unit>
<trans-unit id="InterfaceTypeNotSupportedTitle">
<source>'GeneratedComInterfaceType' does not support the 'ComInterfaceType' value supplied to 'InterfaceTypeAttribute' on the same type.</source>
<target state="new">'GeneratedComInterfaceType' does not support the 'ComInterfaceType' value supplied to 'InterfaceTypeAttribute' on the same type.</target>
<note />
</trans-unit>
<trans-unit id="InvalidAttributedMethodContainingTypeMissingModifiersMessage">
<source>Method '{0}' is contained in a type '{1}' that is not marked 'partial'. P/Invoke source generation will ignore method '{0}'.</source>
<target state="translated">メソッド '{0}' は、'partial' とマークされていない型 '{1}' に含まれています。P/Invoke ソース生成はメソッド '{0}' を無視します。</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,16 @@
<target state="translated">지정된 구성은 소스 생성 P/Invoke에서 지원되지 않습니다.</target>
<note />
</trans-unit>
<trans-unit id="InterfaceTypeNotSupportedMessage">
<source>Using 'GeneratedComInterfaceAttribute' and 'InterfaceTypeAttribute' is not supported with 'ComInterfaceType' value '{0}'.</source>
<target state="new">Using 'GeneratedComInterfaceAttribute' and 'InterfaceTypeAttribute' is not supported with 'ComInterfaceType' value '{0}'.</target>
<note />
</trans-unit>
<trans-unit id="InterfaceTypeNotSupportedTitle">
<source>'GeneratedComInterfaceType' does not support the 'ComInterfaceType' value supplied to 'InterfaceTypeAttribute' on the same type.</source>
<target state="new">'GeneratedComInterfaceType' does not support the 'ComInterfaceType' value supplied to 'InterfaceTypeAttribute' on the same type.</target>
<note />
</trans-unit>
<trans-unit id="InvalidAttributedMethodContainingTypeMissingModifiersMessage">
<source>Method '{0}' is contained in a type '{1}' that is not marked 'partial'. P/Invoke source generation will ignore method '{0}'.</source>
<target state="translated">메서드 '{0}'은(는) 'partial'로 표시되지 않은 '{1}' 형식에 포함되어 있습니다. P/Invoke 소스 생성은 '{0}' 메서드를 무시합니다.</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,16 @@
<target state="translated">Określona konfiguracja nie jest obsługiwana przez funkcję P/Invokes generowaną przez źródło.</target>
<note />
</trans-unit>
<trans-unit id="InterfaceTypeNotSupportedMessage">
<source>Using 'GeneratedComInterfaceAttribute' and 'InterfaceTypeAttribute' is not supported with 'ComInterfaceType' value '{0}'.</source>
<target state="new">Using 'GeneratedComInterfaceAttribute' and 'InterfaceTypeAttribute' is not supported with 'ComInterfaceType' value '{0}'.</target>
<note />
</trans-unit>
<trans-unit id="InterfaceTypeNotSupportedTitle">
<source>'GeneratedComInterfaceType' does not support the 'ComInterfaceType' value supplied to 'InterfaceTypeAttribute' on the same type.</source>
<target state="new">'GeneratedComInterfaceType' does not support the 'ComInterfaceType' value supplied to 'InterfaceTypeAttribute' on the same type.</target>
<note />
</trans-unit>
<trans-unit id="InvalidAttributedMethodContainingTypeMissingModifiersMessage">
<source>Method '{0}' is contained in a type '{1}' that is not marked 'partial'. P/Invoke source generation will ignore method '{0}'.</source>
<target state="translated">Metoda „{0}” jest zawarta w typie „{1}”, który nie jest oznaczony jako „częściowy”. Generowanie źródła funkcji P/Invoke zignoruje metodę „{0}”.</target>
Expand Down
Loading

0 comments on commit 1630725

Please sign in to comment.