File: COMAnalyzer.cs
Web Access
Project: src\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;
            }
        }
    }
}