| File: COMAnalyzer.cs | Web Access |
| Project: src\runtime\src\tools\illink\src\ILLink.RoslynAnalyzer\ILLink.RoslynAnalyzer.csproj (ILLink.RoslynAnalyzer) |
// Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; using System.Collections.Immutable; using System.Runtime.InteropServices; using ILLink.Shared; using ILLink.Shared.TypeSystemProxy; 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); private static readonly DiagnosticDescriptor s_correctnessOfCOMCannotBeGuaranteed = DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.CorrectnessOfCOMCannotBeGuaranteed, helpLinkUri: "https://learn.microsoft.com/dotnet/core/deploying/trim-warnings/il2050"); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(s_correctnessOfCOMCannotBeGuaranteed); public override void Initialize(AnalysisContext context) { context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); if (!System.Diagnostics.Debugger.IsAttached) context.EnableConcurrentExecution(); context.RegisterCompilationStartAction(context => { var compilation = context.Compilation; if (!context.Options.IsMSBuildPropertyValueTrue(MSBuildPropertyOptionNames.EnableTrimAnalyzer)) return; context.RegisterOperationAction(operationContext => { var invocationOperation = (IInvocationOperation)operationContext.Operation; var targetMethod = invocationOperation.TargetMethod; if (!targetMethod.HasAttribute(DllImportAttribute)) return; if (operationContext.ContainingSymbol.IsInRequiresUnreferencedCodeAttributeScope(out _)) 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 is IPointerTypeSymbol) return false; if (typeSymbol == null) return false; if (typeSymbol.IsTypeOf(WellKnownType.System_Array)) { // System.Array marshals as IUnknown by default return true; } else if (typeSymbol.IsTypeOf(WellKnownType.System_String) || typeSymbol.IsTypeOf("System.Text", "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.IsTypeOf("System", "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; } } } }