| File: LibraryImportRequiresExplicitSafetyAnalyzer.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. #if DEBUG using System.Collections.Immutable; using ILLink.Shared; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; namespace ILLink.RoslynAnalyzer { /// <summary> /// Reports <c>IL5007</c> for methods with <c>LibraryImportAttribute</c> that do not declare an explicit /// <c>unsafe</c> or <c>safe</c> contract. /// </summary> /// <remarks> /// The compiler only requires the modifier when the generated implementing part is <c>extern</c>, which /// depends on whether the signature needs marshalling. Requiring it for every shape keeps the contract /// stable and matches what the language asks of <c>extern</c> members. /// <para> /// This is a migration aid for a code base that has not opted into the updated rules yet, so that /// <c>[LibraryImport]</c> methods can be annotated ahead of the switch. Once the rules are on the generator /// reports <c>SYSLIB1064</c> for the same methods, so this analyzer stands down to avoid reporting twice. /// It is disabled by default while this migration tooling remains experimental. /// </para> /// </remarks> [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class LibraryImportRequiresExplicitSafetyAnalyzer : DiagnosticAnalyzer { private const string LibraryImportAttributeNamespace = "System.Runtime.InteropServices"; private const string LibraryImportAttributeName = "LibraryImportAttribute"; private static readonly DiagnosticDescriptor s_rule = DiagnosticDescriptors.GetDiagnosticDescriptor( DiagnosticId.LibraryImportRequiresExplicitSafety, isEnabledByDefault: false); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [s_rule]; public override void Initialize(AnalysisContext context) { context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); if (!System.Diagnostics.Debugger.IsAttached) context.EnableConcurrentExecution(); context.RegisterSymbolAction(AnalyzeMethod, SymbolKind.Method); } private static void AnalyzeMethod(SymbolAnalysisContext context) { var method = (IMethodSymbol)context.Symbol; // Both parts of a partial member are analyzed as separate symbols, so report only from the part the // user authored. if (method.PartialDefinitionPart is not null) return; foreach (AttributeData attribute in method.GetAttributes()) { if (attribute.AttributeClass is not { } attributeClass || !attributeClass.IsTypeOf(LibraryImportAttributeNamespace, LibraryImportAttributeName)) { continue; } // The source generator never copies LibraryImportAttribute onto the implementing part, so the // attribute application always points at the declaration the user authored. if (attribute.ApplicationSyntaxReference is not { } attributeReference || attributeReference.GetSyntax(context.CancellationToken).FirstAncestorOrSelf<MethodDeclarationSyntax>() is not { } declaration // Once the assembly is on the updated rules the generator reports SYSLIB1064 for the same // methods, and this analyzer has nothing left to add. || UnsafeMigrationSyntaxHelpers.UsesUpdatedMemorySafetyRules(declaration.SyntaxTree) || UnsafeMigrationSyntaxHelpers.HasModifier(declaration, SyntaxKind.UnsafeKeyword) || UnsafeMigrationSyntaxHelpers.HasSafeModifier(declaration)) { return; } context.ReportDiagnostic(Diagnostic.Create(s_rule, declaration.Identifier.GetLocation())); return; } } } } #endif