Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/linker.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/ILLink.RoslynAnalyzer/COMAnalyzer.cs117
-rw-r--r--src/ILLink.RoslynAnalyzer/ISymbolExtensions.cs25
-rw-r--r--src/ILLink.Shared/DiagnosticId.cs1
-rw-r--r--src/ILLink.Shared/SharedStrings.resx3
-rw-r--r--test/ILLink.RoslynAnalyzer.Tests/LinkerTestCases.cs7
-rw-r--r--test/ILLink.RoslynAnalyzer.Tests/TestCaseUtils.cs12
-rw-r--r--test/ILLink.RoslynAnalyzer.Tests/TestChecker.cs38
-rw-r--r--test/Mono.Linker.Tests.Cases.Expectations/Assertions/ProducedBy.cs10
-rw-r--r--test/Mono.Linker.Tests.Cases/Interop/PInvoke/Warnings/ComPInvokeWarning.cs24
9 files changed, 227 insertions, 10 deletions
diff --git a/src/ILLink.RoslynAnalyzer/COMAnalyzer.cs b/src/ILLink.RoslynAnalyzer/COMAnalyzer.cs
new file mode 100644
index 000000000..8e7dfe82c
--- /dev/null
+++ b/src/ILLink.RoslynAnalyzer/COMAnalyzer.cs
@@ -0,0 +1,117 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Immutable;
+using System.Runtime.InteropServices;
+using ILLink.Shared;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Operations;
+
+namespace ILLink.RoslynAnalyzer
+{
+ [DiagnosticAnalyzer (LanguageNames.CSharp)]
+ public sealed class COMAnalyzer : DiagnosticAnalyzer
+ {
+ private const string StructLayoutAttribute = nameof (StructLayoutAttribute);
+ private const string DllImportAttribute = nameof (DllImportAttribute);
+ private const string MarshalAsAttribute = nameof (MarshalAsAttribute);
+
+ static readonly DiagnosticDescriptor s_correctnessOfCOMCannotBeGuaranteed = DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.CorrectnessOfCOMCannotBeGuaranteed,
+ helpLinkUri: "https://docs.microsoft.com/en-us/dotnet/core/deploying/trim-warnings/il2050");
+
+ public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create (s_correctnessOfCOMCannotBeGuaranteed);
+
+ public override void Initialize (AnalysisContext context)
+ {
+ context.EnableConcurrentExecution ();
+ context.ConfigureGeneratedCodeAnalysis (GeneratedCodeAnalysisFlags.ReportDiagnostics);
+ context.RegisterCompilationStartAction (context => {
+ var compilation = context.Compilation;
+ if (!context.Options.IsMSBuildPropertyValueTrue (MSBuildPropertyOptionNames.EnableTrimAnalyzer, compilation))
+ return;
+
+ context.RegisterOperationAction (operationContext => {
+ var invocationOperation = (IInvocationOperation) operationContext.Operation;
+ var targetMethod = invocationOperation.TargetMethod;
+ if (!targetMethod.HasAttribute (DllImportAttribute))
+ return;
+
+ bool comDangerousMethod = IsComInterop (targetMethod.ReturnType);
+ foreach (var parameter in targetMethod.Parameters) {
+ comDangerousMethod |= IsComInterop (parameter);
+ }
+
+ if (comDangerousMethod) {
+ operationContext.ReportDiagnostic (Diagnostic.Create (s_correctnessOfCOMCannotBeGuaranteed,
+ operationContext.Operation.Syntax.GetLocation (), targetMethod.GetDisplayName ()));
+ }
+ }, OperationKind.Invocation);
+ });
+
+ static bool IsComInterop (ISymbol symbol)
+ {
+ if (symbol.TryGetAttribute (MarshalAsAttribute, out var marshalAsAttribute) &&
+ marshalAsAttribute.ConstructorArguments.Length >= 1 && marshalAsAttribute.ConstructorArguments[0] is TypedConstant typedConstant &&
+ typedConstant.Type != null && typedConstant.Type.IsUnmanagedType) {
+ var unmanagedType = typedConstant.Value;
+ switch (unmanagedType) {
+ case (int) UnmanagedType.IUnknown:
+ case (int) UnmanagedType.IDispatch:
+ case (int) UnmanagedType.Interface:
+ return true;
+
+ default:
+ if (Enum.IsDefined (typeof (UnmanagedType), unmanagedType))
+ return false;
+
+ break;
+ }
+ }
+
+ if (symbol.IsInterface ())
+ return true;
+
+ ITypeSymbol? typeSymbol = symbol is ITypeSymbol ? symbol as ITypeSymbol : null;
+ if (symbol is IParameterSymbol parameterSymbol)
+ typeSymbol = parameterSymbol.Type;
+
+ if (typeSymbol == null)
+ return false;
+
+ if (typeSymbol.ContainingNamespace.Name == "System" && typeSymbol.Name == "Array") {
+ // System.Array marshals as IUnknown by default
+ return true;
+ } else if (typeSymbol.ContainingNamespace.Name == "System" && typeSymbol.Name == "String" ||
+ typeSymbol.ContainingNamespace.Name == "System.Text" && typeSymbol.Name == "StringBuilder") {
+ // String and StringBuilder are special cased by interop
+ return false;
+ }
+
+ if (typeSymbol.IsValueType) {
+ // Value types don't marshal as COM
+ return false;
+ } else if (typeSymbol.IsInterface ()) {
+ // Interface types marshal as COM by default
+ return true;
+ } else if (typeSymbol.ContainingNamespace.Name == "System" &&
+ typeSymbol.Name == "MulticastDelegate") {
+ // Delegates are special cased by interop
+ return false;
+ } else if (typeSymbol.IsSubclassOf ("System.Runtime.InteropServices", "CriticalHandle") ||
+ typeSymbol.IsSubclassOf ("System.Runtime.InteropServices", "SafeHandle")) {
+ // Subclasses of CriticalHandle and SafeHandle are special cased by interop
+ return false;
+ } else if (typeSymbol.TryGetAttribute (StructLayoutAttribute, out var structLayoutAttribute) &&
+ (LayoutKind) structLayoutAttribute.ConstructorArguments[0].Value! == LayoutKind.Auto) {
+ // Rest of classes that don't have layout marshal as COM
+ return true;
+ }
+
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/ILLink.RoslynAnalyzer/ISymbolExtensions.cs b/src/ILLink.RoslynAnalyzer/ISymbolExtensions.cs
index 71747dcd2..e066d24de 100644
--- a/src/ILLink.RoslynAnalyzer/ISymbolExtensions.cs
+++ b/src/ILLink.RoslynAnalyzer/ISymbolExtensions.cs
@@ -68,5 +68,30 @@ namespace ILLink.RoslynAnalyzer
return sb.ToString ();
}
+
+ public static bool IsInterface (this ISymbol symbol)
+ {
+ if (symbol is not INamedTypeSymbol namedTypeSymbol)
+ return false;
+
+ var typeSymbol = namedTypeSymbol as ITypeSymbol;
+ return typeSymbol.TypeKind == TypeKind.Interface;
+ }
+
+ public static bool IsSubclassOf (this ISymbol symbol, string ns, string type)
+ {
+ if (symbol is not ITypeSymbol typeSymbol)
+ return false;
+
+ while (typeSymbol != null) {
+ if (typeSymbol.ContainingNamespace.Name == ns &&
+ typeSymbol.ContainingType.Name == type)
+ return true;
+
+ typeSymbol = typeSymbol.ContainingType;
+ }
+
+ return false;
+ }
}
}
diff --git a/src/ILLink.Shared/DiagnosticId.cs b/src/ILLink.Shared/DiagnosticId.cs
index c52dcc895..6720edbc4 100644
--- a/src/ILLink.Shared/DiagnosticId.cs
+++ b/src/ILLink.Shared/DiagnosticId.cs
@@ -5,6 +5,7 @@
// Linker diagnostic ids.
RequiresUnreferencedCode = 2026,
RequiresUnreferencedCodeAttributeMismatch = 2046,
+ CorrectnessOfCOMCannotBeGuaranteed = 2050,
MakeGenericType = 2055,
MakeGenericMethod = 2060,
RequiresUnreferencedCodeOnStaticConstructor = 2116,
diff --git a/src/ILLink.Shared/SharedStrings.resx b/src/ILLink.Shared/SharedStrings.resx
index c78c81ede..807f96a88 100644
--- a/src/ILLink.Shared/SharedStrings.resx
+++ b/src/ILLink.Shared/SharedStrings.resx
@@ -183,4 +183,7 @@
<data name="RequiresUnreferencedCodeOnStaticConstructorTitle" xml:space="preserve">
<value>The use of 'RequiresUnreferencedCodeAttribute' on static constructors is disallowed since is a method not callable by the user, is only called by the runtime. Placing the attribute directly on the static constructor will have no effect, instead use 'RequiresUnreferencedCodeAttribute' on the type which will handle warning and silencing from the static constructor.</value>
</data>
+ <data name="CorrectnessOfCOMCannotBeGuaranteedMessage" xml:space="preserve">
+ <value>P/invoke method '{0}' declares a parameter with COM marshalling. Correctness of COM interop cannot be guaranteed after trimming. Interfaces and interface members might be removed.</value>
+ </data>
</root> \ No newline at end of file
diff --git a/test/ILLink.RoslynAnalyzer.Tests/LinkerTestCases.cs b/test/ILLink.RoslynAnalyzer.Tests/LinkerTestCases.cs
index a52400284..37fca09bd 100644
--- a/test/ILLink.RoslynAnalyzer.Tests/LinkerTestCases.cs
+++ b/test/ILLink.RoslynAnalyzer.Tests/LinkerTestCases.cs
@@ -27,5 +27,12 @@ namespace ILLink.RoslynAnalyzer.Tests
RunTest<RequiresUnreferencedCodeAnalyzer> (m, attrs, UseMSBuildProperties (MSBuildPropertyOptionNames.EnableTrimAnalyzer));
}
+
+ [Theory]
+ [MemberData (nameof (TestCaseUtils.GetTestData), parameters: nameof (Interop))]
+ public void Interop (string testName, MethodDeclarationSyntax m, List<AttributeSyntax> attrs)
+ {
+ RunTest<COMAnalyzer> (m, attrs, UseMSBuildProperties (MSBuildPropertyOptionNames.EnableTrimAnalyzer));
+ }
}
}
diff --git a/test/ILLink.RoslynAnalyzer.Tests/TestCaseUtils.cs b/test/ILLink.RoslynAnalyzer.Tests/TestCaseUtils.cs
index 7e5739cad..a1eaa9ecc 100644
--- a/test/ILLink.RoslynAnalyzer.Tests/TestCaseUtils.cs
+++ b/test/ILLink.RoslynAnalyzer.Tests/TestCaseUtils.cs
@@ -18,6 +18,8 @@ namespace ILLink.RoslynAnalyzer.Tests
{
public abstract class TestCaseUtils
{
+ private static readonly string MonoLinkerTestsCases = "Mono.Linker.Tests.Cases";
+
public static readonly ReferenceAssemblies Net6PreviewAssemblies =
new ReferenceAssemblies (
"net6.0",
@@ -86,12 +88,16 @@ namespace ILLink.RoslynAnalyzer.Tests
var builder = ImmutableDictionary.CreateBuilder<string, List<string>> ();
foreach (var file in GetTestFiles ()) {
- var dirName = Path.GetFileName (Path.GetDirectoryName (file))!;
- if (builder.TryGetValue (dirName, out var sources)) {
+ var directory = Path.GetDirectoryName (file);
+ while (Path.GetFileName (Path.GetDirectoryName (directory)) != MonoLinkerTestsCases)
+ directory = Path.GetDirectoryName (directory);
+
+ var parentDirectory = Path.GetFileName (directory);
+ if (builder.TryGetValue (parentDirectory!, out var sources)) {
sources.Add (file);
} else {
sources = new List<string> () { file };
- builder[dirName] = sources;
+ builder[parentDirectory!] = sources;
}
}
diff --git a/test/ILLink.RoslynAnalyzer.Tests/TestChecker.cs b/test/ILLink.RoslynAnalyzer.Tests/TestChecker.cs
index ecf91c376..68563e46e 100644
--- a/test/ILLink.RoslynAnalyzer.Tests/TestChecker.cs
+++ b/test/ILLink.RoslynAnalyzer.Tests/TestChecker.cs
@@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Text;
+using Mono.Linker.Tests.Cases.Expectations.Assertions;
using Xunit;
namespace ILLink.RoslynAnalyzer.Tests
@@ -25,10 +26,15 @@ namespace ILLink.RoslynAnalyzer.Tests
private readonly SyntaxNode MemberSyntax;
+ private readonly string TestingAnalyzerName;
+
public TestChecker (MemberDeclarationSyntax memberSyntax, (CompilationWithAnalyzers Compilation, SemanticModel SemanticModel) compilationResult)
{
Compilation = compilationResult.Compilation;
SemanticModel = compilationResult.SemanticModel;
+
+ // Currently, tests are only run using a single analyzer.
+ TestingAnalyzerName = Compilation.Analyzers.Single ().GetType ().Name;
DiagnosticMessages = Compilation.GetAnalyzerDiagnosticsAsync ().Result
.Where (d => {
// Filter down to diagnostics which originate from this member.
@@ -67,11 +73,11 @@ namespace ILLink.RoslynAnalyzer.Tests
switch (attribute.Name.ToString ()) {
case "ExpectedWarning":
var args = TestCaseUtils.GetAttributeArguments (attribute);
- if (args.TryGetValue ("ProducedBy", out var producedBy) &&
- producedBy is MemberAccessExpressionSyntax memberAccessExpression &&
- memberAccessExpression.Name is IdentifierNameSyntax identifierNameSyntax &&
- identifierNameSyntax.Identifier.ValueText == "Trimmer")
- return false;
+ if (args.TryGetValue ("ProducedBy", out var producedBy)) {
+ // Skip if this warning is not expected to be produced by any of the analyzers that we are currently testing.
+ return GetProducedBy (producedBy).HasFlag (Enum.Parse<ProducedBy> (TestingAnalyzerName));
+ }
+
return true;
case "LogContains":
case "UnrecognizedReflectionAccessPattern":
@@ -79,6 +85,28 @@ namespace ILLink.RoslynAnalyzer.Tests
default:
return false;
}
+
+ static ProducedBy GetProducedBy (ExpressionSyntax expression)
+ {
+ var producedBy = (ProducedBy) 0x0;
+ switch (expression) {
+ case BinaryExpressionSyntax binaryExpressionSyntax:
+ Enum.TryParse<ProducedBy> ((binaryExpressionSyntax.Left as MemberAccessExpressionSyntax)!.Name.Identifier.ValueText, out var besProducedBy);
+ producedBy |= besProducedBy;
+ producedBy |= GetProducedBy (binaryExpressionSyntax.Right);
+ break;
+
+ case MemberAccessExpressionSyntax memberAccessExpressionSyntax:
+ Enum.TryParse<ProducedBy> (memberAccessExpressionSyntax.Name.Identifier.ValueText, out var maeProducedBy);
+ producedBy |= maeProducedBy;
+ break;
+
+ default:
+ break;
+ }
+
+ return producedBy;
+ }
}
bool TryValidateExpectedDiagnostic (AttributeSyntax attribute, List<Diagnostic> diagnostics, [NotNullWhen (true)] out int? matchIndex, [NotNullWhen (false)] out string? missingDiagnosticMessage)
diff --git a/test/Mono.Linker.Tests.Cases.Expectations/Assertions/ProducedBy.cs b/test/Mono.Linker.Tests.Cases.Expectations/Assertions/ProducedBy.cs
index b18578c3d..6b441975e 100644
--- a/test/Mono.Linker.Tests.Cases.Expectations/Assertions/ProducedBy.cs
+++ b/test/Mono.Linker.Tests.Cases.Expectations/Assertions/ProducedBy.cs
@@ -5,11 +5,19 @@ using System;
namespace Mono.Linker.Tests.Cases.Expectations.Assertions
{
+ /// <summary>
+ /// Used to specify which tool produces a warning. This can either be the trimmer, a specific analyzer, or both.
+ /// Currently we have all existing diagnostic analyzers listed in here so that we can leave out some expected warnings
+ /// when testing analyzers which do not produce them.
+ /// </summary>
[Flags]
public enum ProducedBy
{
Trimmer = 1,
- Analyzer = 2,
+ RequiresAssemblyFileAnalyzer = 2,
+ RequiresUnreferencedCodeAnalyzer = 4,
+ COMAnalyzer = 8,
+ Analyzer = RequiresAssemblyFileAnalyzer | RequiresUnreferencedCodeAnalyzer | COMAnalyzer,
TrimmerAndAnalyzer = Trimmer | Analyzer
}
} \ No newline at end of file
diff --git a/test/Mono.Linker.Tests.Cases/Interop/PInvoke/Warnings/ComPInvokeWarning.cs b/test/Mono.Linker.Tests.Cases/Interop/PInvoke/Warnings/ComPInvokeWarning.cs
index 720ad738b..e82d8bc59 100644
--- a/test/Mono.Linker.Tests.Cases/Interop/PInvoke/Warnings/ComPInvokeWarning.cs
+++ b/test/Mono.Linker.Tests.Cases/Interop/PInvoke/Warnings/ComPInvokeWarning.cs
@@ -15,6 +15,7 @@ namespace Mono.Linker.Tests.Cases.Interop.PInvoke.Warnings
[UnconditionalSuppressMessage ("trim", "IL2026")]
static void Main ()
{
+ Call_SomeMethodReturningAutoLayoutClass ();
Call_SomeMethodTakingInterface ();
Call_SomeMethodTakingObject ();
Call_SomeMethodTakingArray ();
@@ -38,6 +39,7 @@ namespace Mono.Linker.Tests.Cases.Interop.PInvoke.Warnings
{
SomeMethodTakingInterface (null);
}
+
[DllImport ("Foo")]
static extern void SomeMethodTakingInterface (IFoo foo);
@@ -46,6 +48,7 @@ namespace Mono.Linker.Tests.Cases.Interop.PInvoke.Warnings
{
SomeMethodTakingObject (null);
}
+
[DllImport ("Foo")]
static extern void SomeMethodTakingObject ([MarshalAs (UnmanagedType.IUnknown)] object obj);
@@ -54,6 +57,7 @@ namespace Mono.Linker.Tests.Cases.Interop.PInvoke.Warnings
{
SomeMethodTakingArray (null);
}
+
[DllImport ("Foo")]
static extern void SomeMethodTakingArray (Array array);
@@ -61,6 +65,7 @@ namespace Mono.Linker.Tests.Cases.Interop.PInvoke.Warnings
{
SomeMethodTakingStringBuilder (null);
}
+
[DllImport ("Foo")]
static extern void SomeMethodTakingStringBuilder (StringBuilder str);
@@ -68,6 +73,7 @@ namespace Mono.Linker.Tests.Cases.Interop.PInvoke.Warnings
{
SomeMethodTakingCriticalHandle (null);
}
+
[DllImport ("Foo")]
static extern void SomeMethodTakingCriticalHandle (MyCriticalHandle handle);
@@ -76,6 +82,7 @@ namespace Mono.Linker.Tests.Cases.Interop.PInvoke.Warnings
{
SomeMethodTakingSafeHandle (null);
}
+
[DllImport ("Foo")]
static extern void SomeMethodTakingSafeHandle (TestSafeHandle handle);
@@ -83,6 +90,7 @@ namespace Mono.Linker.Tests.Cases.Interop.PInvoke.Warnings
{
SomeMethodTakingExplicitLayout (null);
}
+
[DllImport ("Foo")]
static extern void SomeMethodTakingExplicitLayout (ExplicitLayout _class);
@@ -90,6 +98,7 @@ namespace Mono.Linker.Tests.Cases.Interop.PInvoke.Warnings
{
SomeMethodTakingSequentialLayout (null);
}
+
[DllImport ("Foo")]
static extern void SomeMethodTakingSequentialLayout (SequentialLayout _class);
@@ -98,14 +107,24 @@ namespace Mono.Linker.Tests.Cases.Interop.PInvoke.Warnings
{
SomeMethodTakingAutoLayout (null);
}
+
[DllImport ("Foo")]
static extern void SomeMethodTakingAutoLayout (AutoLayout _class);
+ [ExpectedWarning ("IL2050")]
+ static void Call_SomeMethodReturningAutoLayoutClass ()
+ {
+ SomeMethodReturningAutoLayout ();
+ }
+
+ [DllImport ("Foo")]
+ static extern AutoLayout SomeMethodReturningAutoLayout ();
static void Call_SomeMethodTakingString ()
{
SomeMethodTakingString (null);
}
+
[DllImport ("Foo")]
static extern void SomeMethodTakingString (String str);
@@ -114,6 +133,7 @@ namespace Mono.Linker.Tests.Cases.Interop.PInvoke.Warnings
{
GetInterface ();
}
+
[DllImport ("Foo")]
static extern IFoo GetInterface ();
@@ -122,6 +142,7 @@ namespace Mono.Linker.Tests.Cases.Interop.PInvoke.Warnings
{
CanSuppressWarningOnParameter (null);
}
+
[DllImport ("Foo")]
static extern void CanSuppressWarningOnParameter ([MarshalAs (UnmanagedType.IUnknown)] object obj);
@@ -130,6 +151,7 @@ namespace Mono.Linker.Tests.Cases.Interop.PInvoke.Warnings
{
CanSuppressWarningOnReturnType ();
}
+
[DllImport ("Foo")]
static extern IFoo CanSuppressWarningOnReturnType ();
@@ -153,7 +175,7 @@ namespace Mono.Linker.Tests.Cases.Interop.PInvoke.Warnings
static extern void CanSuppressPInvokeWithRequiresUnreferencedCode (IFoo foo);
[ExpectedWarning ("IL2050")]
- [ExpectedWarning ("IL2026")]
+ [ExpectedWarning ("IL2026", ProducedBy = ProducedBy.Trimmer | ProducedBy.RequiresUnreferencedCodeAnalyzer)]
static void Call_PInvokeWithRequiresUnreferencedCode ()
{
PInvokeWithRequiresUnreferencedCode (null);