|
// 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;
}
}
}
}
|