diff --git a/src/ILLink.RoslynAnalyzer/AnalyzerOptionsExtensions.cs b/src/ILLink.RoslynAnalyzer/AnalyzerOptionsExtensions.cs new file mode 100644 index 000000000000..f5781802fd8e --- /dev/null +++ b/src/ILLink.RoslynAnalyzer/AnalyzerOptionsExtensions.cs @@ -0,0 +1,31 @@ +// 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.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace ILLink.RoslynAnalyzer +{ + internal static class AnalyzerOptionsExtensions + { + public static string? GetMSBuildPropertyValue ( + this AnalyzerOptions options, + string optionName, + Compilation compilation) + { + // MSBuild property values should be set at compilation level, and cannot have different values per-tree. + // So, we default to first syntax tree. + var tree = compilation.SyntaxTrees.FirstOrDefault (); + if (tree is null) { + return null; + } + + return options.AnalyzerConfigOptionsProvider.GlobalOptions.TryGetValue ( + $"build_property.{optionName}", out var value) + ? value + : null; + } + } +} diff --git a/src/ILLink.RoslynAnalyzer/DiagnosticCategory.cs b/src/ILLink.RoslynAnalyzer/DiagnosticCategory.cs new file mode 100644 index 000000000000..28cf0f031436 --- /dev/null +++ b/src/ILLink.RoslynAnalyzer/DiagnosticCategory.cs @@ -0,0 +1,12 @@ +// 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. + +namespace ILLink.RoslynAnalyzer +{ + internal static class DiagnosticCategory + { + public const string SingleFile = nameof (SingleFile); + public const string Trimming = nameof (Trimming); + } +} diff --git a/src/ILLink.RoslynAnalyzer/MSBuildPropertyOptionNames.cs b/src/ILLink.RoslynAnalyzer/MSBuildPropertyOptionNames.cs new file mode 100644 index 000000000000..1f0b40459149 --- /dev/null +++ b/src/ILLink.RoslynAnalyzer/MSBuildPropertyOptionNames.cs @@ -0,0 +1,13 @@ +// 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. + +namespace ILLink.RoslynAnalyzer +{ + public static class MSBuildPropertyOptionNames + { + public const string PublishSingleFile = nameof (PublishSingleFile); + public const string IncludeAllContentForSelfExtract = nameof (IncludeAllContentForSelfExtract); + public const string PublishTrimmed = nameof (PublishTrimmed); + } +} diff --git a/src/ILLink.RoslynAnalyzer/RequiresUnreferencedCodeAnalyzer.cs b/src/ILLink.RoslynAnalyzer/RequiresUnreferencedCodeAnalyzer.cs index 1dd287114d27..8b9c1cd7bfef 100644 --- a/src/ILLink.RoslynAnalyzer/RequiresUnreferencedCodeAnalyzer.cs +++ b/src/ILLink.RoslynAnalyzer/RequiresUnreferencedCodeAnalyzer.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Immutable; -using System.Diagnostics; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; @@ -16,21 +15,13 @@ public class RequiresUnreferencedCodeAnalyzer : DiagnosticAnalyzer { public const string DiagnosticId = "IL2026"; - private static readonly LocalizableString s_title = new LocalizableResourceString ( - nameof (RequiresUnreferencedCodeAnalyzer) + "Title", - Resources.ResourceManager, - typeof (Resources)); - private static readonly LocalizableString s_messageFormat = new LocalizableResourceString ( - nameof (RequiresUnreferencedCodeAnalyzer) + "Message", - Resources.ResourceManager, - typeof (Resources)); - private const string s_category = "Trimming"; - private static readonly DiagnosticDescriptor s_rule = new DiagnosticDescriptor ( DiagnosticId, - s_title, - s_messageFormat, - s_category, + new LocalizableResourceString (nameof (Resources.RequiresUnreferencedCodeAnalyzerTitle), + Resources.ResourceManager, typeof (Resources)), + new LocalizableResourceString (nameof (Resources.RequiresUnreferencedCodeAnalyzerMessage), + Resources.ResourceManager, typeof (Resources)), + DiagnosticCategory.Trimming, DiagnosticSeverity.Warning, isEnabledByDefault: true); @@ -44,8 +35,16 @@ public override void Initialize (AnalysisContext context) context.RegisterCompilationStartAction (context => { var compilation = context.Compilation; + var isPublishTrimmed = context.Options.GetMSBuildPropertyValue (MSBuildPropertyOptionNames.PublishTrimmed, compilation); + if (!string.Equals (isPublishTrimmed?.Trim (), "true", StringComparison.OrdinalIgnoreCase)) { + return; + } + context.RegisterOperationAction (operationContext => { var call = (IInvocationOperation) operationContext.Operation; + if (call.IsVirtual && call.TargetMethod.OverriddenMethod != null) + return; + CheckMethodOrCtorCall (operationContext, call.TargetMethod, call.Syntax.GetLocation ()); }, OperationKind.Invocation); diff --git a/src/ILLink.RoslynAnalyzer/Resources.resx b/src/ILLink.RoslynAnalyzer/Resources.resx index 0c0780e4dbec..202514e6dcc2 100644 --- a/src/ILLink.RoslynAnalyzer/Resources.resx +++ b/src/ILLink.RoslynAnalyzer/Resources.resx @@ -123,4 +123,16 @@ Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}. {2} + + Avoid accessing Assembly file path when publishing as a single file + + + '{0}' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. + + + Avoid accessing Assembly file path when publishing as a single file + + + '{0}' will throw for assemblies embedded in a single-file app + \ No newline at end of file diff --git a/src/ILLink.RoslynAnalyzer/SingleFileAnalyzer.cs b/src/ILLink.RoslynAnalyzer/SingleFileAnalyzer.cs new file mode 100644 index 000000000000..536cd82f266c --- /dev/null +++ b/src/ILLink.RoslynAnalyzer/SingleFileAnalyzer.cs @@ -0,0 +1,141 @@ +// 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.Generic; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; + +namespace ILLink.RoslynAnalyzer +{ + /// + /// IL3000, IL3001: Do not use Assembly file path in single-file publish + /// + [DiagnosticAnalyzer (LanguageNames.CSharp, LanguageNames.VisualBasic)] + public sealed class AvoidAssemblyLocationInSingleFile : DiagnosticAnalyzer + { + public const string IL3000 = nameof (IL3000); + public const string IL3001 = nameof (IL3001); + + private static readonly DiagnosticDescriptor LocationRule = new DiagnosticDescriptor ( + IL3000, + new LocalizableResourceString (nameof (Resources.AvoidAssemblyLocationInSingleFileTitle), + Resources.ResourceManager, typeof (Resources)), + new LocalizableResourceString (nameof (Resources.AvoidAssemblyLocationInSingleFileMessage), + Resources.ResourceManager, typeof (Resources)), + DiagnosticCategory.SingleFile, + DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + private static readonly DiagnosticDescriptor GetFilesRule = new DiagnosticDescriptor ( + IL3001, + new LocalizableResourceString (nameof (Resources.AvoidAssemblyGetFilesInSingleFileTitle), + Resources.ResourceManager, typeof (Resources)), + new LocalizableResourceString (nameof (Resources.AvoidAssemblyGetFilesInSingleFileMessage), + Resources.ResourceManager, typeof (Resources)), + DiagnosticCategory.SingleFile, + DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create (LocationRule, GetFilesRule); + + public override void Initialize (AnalysisContext context) + { + context.EnableConcurrentExecution (); + context.ConfigureGeneratedCodeAnalysis (GeneratedCodeAnalysisFlags.ReportDiagnostics); + + context.RegisterCompilationStartAction (context => { + var compilation = context.Compilation; + + var isSingleFilePublish = context.Options.GetMSBuildPropertyValue (MSBuildPropertyOptionNames.PublishSingleFile, compilation); + if (!string.Equals (isSingleFilePublish?.Trim (), "true", StringComparison.OrdinalIgnoreCase)) { + return; + } + var includesAllContent = context.Options.GetMSBuildPropertyValue (MSBuildPropertyOptionNames.IncludeAllContentForSelfExtract, compilation); + if (string.Equals (includesAllContent?.Trim (), "true", StringComparison.OrdinalIgnoreCase)) { + return; + } + + var propertiesBuilder = ImmutableArray.CreateBuilder (); + var methodsBuilder = ImmutableArray.CreateBuilder (); + + var assemblyType = compilation.GetTypeByMetadataName ("System.Reflection.Assembly"); + if (assemblyType != null) { + // properties + AddIfNotNull (propertiesBuilder, TryGetSingleSymbol (assemblyType.GetMembers ("Location"))); + + // methods + methodsBuilder.AddRange (assemblyType.GetMembers ("GetFile").OfType ()); + methodsBuilder.AddRange (assemblyType.GetMembers ("GetFiles").OfType ()); + } + + var assemblyNameType = compilation.GetTypeByMetadataName ("System.Reflection.AssemblyName"); + if (assemblyNameType != null) { + AddIfNotNull (propertiesBuilder, TryGetSingleSymbol (assemblyNameType.GetMembers ("CodeBase"))); + AddIfNotNull (propertiesBuilder, TryGetSingleSymbol (assemblyNameType.GetMembers ("EscapedCodeBase"))); + } + + var properties = propertiesBuilder.ToImmutable (); + var methods = methodsBuilder.ToImmutable (); + + context.RegisterOperationAction (operationContext => { + var access = (IPropertyReferenceOperation) operationContext.Operation; + var property = access.Property; + if (!Contains (properties, property, SymbolEqualityComparer.Default)) { + return; + } + + operationContext.ReportDiagnostic (Diagnostic.Create (LocationRule, access.Syntax.GetLocation (), property)); + }, OperationKind.PropertyReference); + + context.RegisterOperationAction (operationContext => { + var invocation = (IInvocationOperation) operationContext.Operation; + var targetMethod = invocation.TargetMethod; + if (!Contains (methods, targetMethod, SymbolEqualityComparer.Default)) { + return; + } + + operationContext.ReportDiagnostic (Diagnostic.Create (GetFilesRule, invocation.Syntax.GetLocation (), targetMethod)); + }, OperationKind.Invocation); + + return; + + static bool Contains (ImmutableArray list, T elem, TComp comparer) + where TComp : IEqualityComparer + { + foreach (var e in list) { + if (comparer.Equals (e, elem)) { + return true; + } + } + return false; + } + + static TSymbol? TryGetSingleSymbol (ImmutableArray members) where TSymbol : class, ISymbol + { + TSymbol? candidate = null; + foreach (var m in members) { + if (m is TSymbol tsym) { + if (candidate is null) { + candidate = tsym; + } else { + return null; + } + } + } + return candidate; + } + + static void AddIfNotNull (ImmutableArray.Builder properties, TSymbol? p) where TSymbol : class, ISymbol + { + if (p != null) { + properties.Add (p); + } + } + }); + } + } +} diff --git a/src/ILLink.RoslynAnalyzer/xlf/Resources.cs.xlf b/src/ILLink.RoslynAnalyzer/xlf/Resources.cs.xlf index 5c5e4af14fd7..1c0aab648942 100644 --- a/src/ILLink.RoslynAnalyzer/xlf/Resources.cs.xlf +++ b/src/ILLink.RoslynAnalyzer/xlf/Resources.cs.xlf @@ -2,6 +2,26 @@ + + '{0}' will throw for assemblies embedded in a single-file app + '{0}' will throw for assemblies embedded in a single-file app + + + + Avoid accessing Assembly file path when publishing as a single file + Avoid accessing Assembly file path when publishing as a single file + + + + '{0}' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. + '{0}' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. + + + + Avoid accessing Assembly file path when publishing as a single file + Avoid accessing Assembly file path when publishing as a single file + + Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}. {2} Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}. {2} diff --git a/src/ILLink.RoslynAnalyzer/xlf/Resources.de.xlf b/src/ILLink.RoslynAnalyzer/xlf/Resources.de.xlf index a80b9d4fa2ab..30f09f0f37c5 100644 --- a/src/ILLink.RoslynAnalyzer/xlf/Resources.de.xlf +++ b/src/ILLink.RoslynAnalyzer/xlf/Resources.de.xlf @@ -2,6 +2,26 @@ + + '{0}' will throw for assemblies embedded in a single-file app + '{0}' will throw for assemblies embedded in a single-file app + + + + Avoid accessing Assembly file path when publishing as a single file + Avoid accessing Assembly file path when publishing as a single file + + + + '{0}' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. + '{0}' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. + + + + Avoid accessing Assembly file path when publishing as a single file + Avoid accessing Assembly file path when publishing as a single file + + Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}. {2} Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}. {2} diff --git a/src/ILLink.RoslynAnalyzer/xlf/Resources.es.xlf b/src/ILLink.RoslynAnalyzer/xlf/Resources.es.xlf index 46cf191753a5..10648e7f4ab4 100644 --- a/src/ILLink.RoslynAnalyzer/xlf/Resources.es.xlf +++ b/src/ILLink.RoslynAnalyzer/xlf/Resources.es.xlf @@ -2,6 +2,26 @@ + + '{0}' will throw for assemblies embedded in a single-file app + '{0}' will throw for assemblies embedded in a single-file app + + + + Avoid accessing Assembly file path when publishing as a single file + Avoid accessing Assembly file path when publishing as a single file + + + + '{0}' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. + '{0}' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. + + + + Avoid accessing Assembly file path when publishing as a single file + Avoid accessing Assembly file path when publishing as a single file + + Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}. {2} Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}. {2} diff --git a/src/ILLink.RoslynAnalyzer/xlf/Resources.fr.xlf b/src/ILLink.RoslynAnalyzer/xlf/Resources.fr.xlf index c147c8c1d4e2..1d419581715d 100644 --- a/src/ILLink.RoslynAnalyzer/xlf/Resources.fr.xlf +++ b/src/ILLink.RoslynAnalyzer/xlf/Resources.fr.xlf @@ -2,6 +2,26 @@ + + '{0}' will throw for assemblies embedded in a single-file app + '{0}' will throw for assemblies embedded in a single-file app + + + + Avoid accessing Assembly file path when publishing as a single file + Avoid accessing Assembly file path when publishing as a single file + + + + '{0}' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. + '{0}' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. + + + + Avoid accessing Assembly file path when publishing as a single file + Avoid accessing Assembly file path when publishing as a single file + + Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}. {2} Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}. {2} diff --git a/src/ILLink.RoslynAnalyzer/xlf/Resources.it.xlf b/src/ILLink.RoslynAnalyzer/xlf/Resources.it.xlf index 6c3fbcd90850..5090e344e4e0 100644 --- a/src/ILLink.RoslynAnalyzer/xlf/Resources.it.xlf +++ b/src/ILLink.RoslynAnalyzer/xlf/Resources.it.xlf @@ -2,6 +2,26 @@ + + '{0}' will throw for assemblies embedded in a single-file app + '{0}' will throw for assemblies embedded in a single-file app + + + + Avoid accessing Assembly file path when publishing as a single file + Avoid accessing Assembly file path when publishing as a single file + + + + '{0}' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. + '{0}' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. + + + + Avoid accessing Assembly file path when publishing as a single file + Avoid accessing Assembly file path when publishing as a single file + + Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}. {2} Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}. {2} diff --git a/src/ILLink.RoslynAnalyzer/xlf/Resources.ja.xlf b/src/ILLink.RoslynAnalyzer/xlf/Resources.ja.xlf index 38814260cc4f..8c07ebe5de9d 100644 --- a/src/ILLink.RoslynAnalyzer/xlf/Resources.ja.xlf +++ b/src/ILLink.RoslynAnalyzer/xlf/Resources.ja.xlf @@ -2,6 +2,26 @@ + + '{0}' will throw for assemblies embedded in a single-file app + '{0}' will throw for assemblies embedded in a single-file app + + + + Avoid accessing Assembly file path when publishing as a single file + Avoid accessing Assembly file path when publishing as a single file + + + + '{0}' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. + '{0}' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. + + + + Avoid accessing Assembly file path when publishing as a single file + Avoid accessing Assembly file path when publishing as a single file + + Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}. {2} Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}. {2} diff --git a/src/ILLink.RoslynAnalyzer/xlf/Resources.ko.xlf b/src/ILLink.RoslynAnalyzer/xlf/Resources.ko.xlf index db861065b59c..363b9c42754f 100644 --- a/src/ILLink.RoslynAnalyzer/xlf/Resources.ko.xlf +++ b/src/ILLink.RoslynAnalyzer/xlf/Resources.ko.xlf @@ -2,6 +2,26 @@ + + '{0}' will throw for assemblies embedded in a single-file app + '{0}' will throw for assemblies embedded in a single-file app + + + + Avoid accessing Assembly file path when publishing as a single file + Avoid accessing Assembly file path when publishing as a single file + + + + '{0}' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. + '{0}' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. + + + + Avoid accessing Assembly file path when publishing as a single file + Avoid accessing Assembly file path when publishing as a single file + + Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}. {2} Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}. {2} diff --git a/src/ILLink.RoslynAnalyzer/xlf/Resources.pl.xlf b/src/ILLink.RoslynAnalyzer/xlf/Resources.pl.xlf index 7ba7c0e2778f..927764ec5872 100644 --- a/src/ILLink.RoslynAnalyzer/xlf/Resources.pl.xlf +++ b/src/ILLink.RoslynAnalyzer/xlf/Resources.pl.xlf @@ -2,6 +2,26 @@ + + '{0}' will throw for assemblies embedded in a single-file app + '{0}' will throw for assemblies embedded in a single-file app + + + + Avoid accessing Assembly file path when publishing as a single file + Avoid accessing Assembly file path when publishing as a single file + + + + '{0}' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. + '{0}' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. + + + + Avoid accessing Assembly file path when publishing as a single file + Avoid accessing Assembly file path when publishing as a single file + + Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}. {2} Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}. {2} diff --git a/src/ILLink.RoslynAnalyzer/xlf/Resources.pt-BR.xlf b/src/ILLink.RoslynAnalyzer/xlf/Resources.pt-BR.xlf index 3c748ed32040..f0c021bcc043 100644 --- a/src/ILLink.RoslynAnalyzer/xlf/Resources.pt-BR.xlf +++ b/src/ILLink.RoslynAnalyzer/xlf/Resources.pt-BR.xlf @@ -2,6 +2,26 @@ + + '{0}' will throw for assemblies embedded in a single-file app + '{0}' will throw for assemblies embedded in a single-file app + + + + Avoid accessing Assembly file path when publishing as a single file + Avoid accessing Assembly file path when publishing as a single file + + + + '{0}' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. + '{0}' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. + + + + Avoid accessing Assembly file path when publishing as a single file + Avoid accessing Assembly file path when publishing as a single file + + Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}. {2} Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}. {2} diff --git a/src/ILLink.RoslynAnalyzer/xlf/Resources.ru.xlf b/src/ILLink.RoslynAnalyzer/xlf/Resources.ru.xlf index 4820e55e87d2..afbc27f9bb57 100644 --- a/src/ILLink.RoslynAnalyzer/xlf/Resources.ru.xlf +++ b/src/ILLink.RoslynAnalyzer/xlf/Resources.ru.xlf @@ -2,6 +2,26 @@ + + '{0}' will throw for assemblies embedded in a single-file app + '{0}' will throw for assemblies embedded in a single-file app + + + + Avoid accessing Assembly file path when publishing as a single file + Avoid accessing Assembly file path when publishing as a single file + + + + '{0}' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. + '{0}' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. + + + + Avoid accessing Assembly file path when publishing as a single file + Avoid accessing Assembly file path when publishing as a single file + + Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}. {2} Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}. {2} diff --git a/src/ILLink.RoslynAnalyzer/xlf/Resources.tr.xlf b/src/ILLink.RoslynAnalyzer/xlf/Resources.tr.xlf index 9b85a9a3b42a..c1cf60671f02 100644 --- a/src/ILLink.RoslynAnalyzer/xlf/Resources.tr.xlf +++ b/src/ILLink.RoslynAnalyzer/xlf/Resources.tr.xlf @@ -2,6 +2,26 @@ + + '{0}' will throw for assemblies embedded in a single-file app + '{0}' will throw for assemblies embedded in a single-file app + + + + Avoid accessing Assembly file path when publishing as a single file + Avoid accessing Assembly file path when publishing as a single file + + + + '{0}' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. + '{0}' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. + + + + Avoid accessing Assembly file path when publishing as a single file + Avoid accessing Assembly file path when publishing as a single file + + Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}. {2} Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}. {2} diff --git a/src/ILLink.RoslynAnalyzer/xlf/Resources.zh-Hans.xlf b/src/ILLink.RoslynAnalyzer/xlf/Resources.zh-Hans.xlf index 80c654eb58f7..7ca61b715a9c 100644 --- a/src/ILLink.RoslynAnalyzer/xlf/Resources.zh-Hans.xlf +++ b/src/ILLink.RoslynAnalyzer/xlf/Resources.zh-Hans.xlf @@ -2,6 +2,26 @@ + + '{0}' will throw for assemblies embedded in a single-file app + '{0}' will throw for assemblies embedded in a single-file app + + + + Avoid accessing Assembly file path when publishing as a single file + Avoid accessing Assembly file path when publishing as a single file + + + + '{0}' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. + '{0}' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. + + + + Avoid accessing Assembly file path when publishing as a single file + Avoid accessing Assembly file path when publishing as a single file + + Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}. {2} Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}. {2} diff --git a/src/ILLink.RoslynAnalyzer/xlf/Resources.zh-Hant.xlf b/src/ILLink.RoslynAnalyzer/xlf/Resources.zh-Hant.xlf index 80d60776bf79..64d9339788d9 100644 --- a/src/ILLink.RoslynAnalyzer/xlf/Resources.zh-Hant.xlf +++ b/src/ILLink.RoslynAnalyzer/xlf/Resources.zh-Hant.xlf @@ -2,6 +2,26 @@ + + '{0}' will throw for assemblies embedded in a single-file app + '{0}' will throw for assemblies embedded in a single-file app + + + + Avoid accessing Assembly file path when publishing as a single file + Avoid accessing Assembly file path when publishing as a single file + + + + '{0}' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. + '{0}' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. + + + + Avoid accessing Assembly file path when publishing as a single file + Avoid accessing Assembly file path when publishing as a single file + + Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}. {2} Calling '{0}' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. {1}. {2} diff --git a/test/ILLink.RoslynAnalyzer.Tests/AvoidAssemblyLocationInSingleFileTests.cs b/test/ILLink.RoslynAnalyzer.Tests/AvoidAssemblyLocationInSingleFileTests.cs new file mode 100644 index 000000000000..0ea712f9751f --- /dev/null +++ b/test/ILLink.RoslynAnalyzer.Tests/AvoidAssemblyLocationInSingleFileTests.cs @@ -0,0 +1,144 @@ +// 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.Threading.Tasks; +using Microsoft.CodeAnalysis.Testing; +using Xunit; +using VerifyCS = ILLink.RoslynAnalyzer.Tests.CSharpAnalyzerVerifier< + ILLink.RoslynAnalyzer.AvoidAssemblyLocationInSingleFile>; + +namespace ILLink.RoslynAnalyzer.Tests +{ + public class AvoidAssemblyLocationInSingleFileTests + { + static Task VerifySingleFileAnalyzer (string source, params DiagnosticResult[] expected) + { + return VerifyCS.VerifyAnalyzerAsync (source, + TestCaseUtils.UseMSBuildProperties (MSBuildPropertyOptionNames.PublishSingleFile), + expected); + } + + [Fact] + public Task GetExecutingAssemblyLocation () + { + const string src = @" +using System.Reflection; +class C +{ + public string M() => Assembly.GetExecutingAssembly().Location; +}"; + + return VerifySingleFileAnalyzer (src, + // (5,26): warning IL3000: 'System.Reflection.Assembly.Location' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. + VerifyCS.Diagnostic (AvoidAssemblyLocationInSingleFile.IL3000).WithSpan (5, 26, 5, 66).WithArguments ("System.Reflection.Assembly.Location")); + } + + [Fact] + public Task AssemblyProperties () + { + var src = @" +using System.Reflection; +class C +{ + public void M() + { + var a = Assembly.GetExecutingAssembly(); + _ = a.Location; + // below methods are marked as obsolete in 5.0 + // _ = a.CodeBase; + // _ = a.EscapedCodeBase; + } +}"; + return VerifySingleFileAnalyzer (src, + // (8,13): warning IL3000: 'System.Reflection.Assembly.Location' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. + VerifyCS.Diagnostic (AvoidAssemblyLocationInSingleFile.IL3000).WithSpan (8, 13, 8, 23).WithArguments ("System.Reflection.Assembly.Location") + ); + } + + [Fact] + public Task AssemblyMethods () + { + var src = @" +using System.Reflection; +class C +{ + public void M() + { + var a = Assembly.GetExecutingAssembly(); + _ = a.GetFile(""/some/file/path""); + _ = a.GetFiles(); + } +}"; + return VerifySingleFileAnalyzer (src, + // (8,13): warning IL3001: Assemblies embedded in a single-file app cannot have additional files in the manifest. + VerifyCS.Diagnostic (AvoidAssemblyLocationInSingleFile.IL3001).WithSpan (8, 13, 8, 41).WithArguments ("System.Reflection.Assembly.GetFile(string)"), + // (9,13): warning IL3001: Assemblies embedded in a single-file app cannot have additional files in the manifest. + VerifyCS.Diagnostic (AvoidAssemblyLocationInSingleFile.IL3001).WithSpan (9, 13, 9, 25).WithArguments ("System.Reflection.Assembly.GetFiles()") + ); + } + + [Fact] + public Task AssemblyNameAttributes () + { + var src = @" +using System.Reflection; +class C +{ + public void M() + { + var a = Assembly.GetExecutingAssembly().GetName(); + _ = a.CodeBase; + _ = a.EscapedCodeBase; + } +}"; + return VerifySingleFileAnalyzer (src, + // (8,13): warning IL3000: 'System.Reflection.AssemblyName.CodeBase' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. + VerifyCS.Diagnostic (AvoidAssemblyLocationInSingleFile.IL3000).WithSpan (8, 13, 8, 23).WithArguments ("System.Reflection.AssemblyName.CodeBase"), + // (9,13): warning IL3000: 'System.Reflection.AssemblyName.EscapedCodeBase' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. + VerifyCS.Diagnostic (AvoidAssemblyLocationInSingleFile.IL3000).WithSpan (9, 13, 9, 30).WithArguments ("System.Reflection.AssemblyName.EscapedCodeBase") + ); + } + + [Fact] + public Task FalsePositive () + { + // This is an OK use of Location and GetFile since these assemblies were loaded from + // a file, but the analyzer is conservative + var src = @" +using System.Reflection; +class C +{ + public void M() + { + var a = Assembly.LoadFrom(""/some/path/not/in/bundle""); + _ = a.Location; + _ = a.GetFiles(); + } +}"; + return VerifySingleFileAnalyzer (src, + // (8,13): warning IL3000: 'System.Reflection.Assembly.Location' always returns an empty string for assemblies embedded in a single-file app. If the path to the app directory is needed, consider calling 'System.AppContext.BaseDirectory'. + VerifyCS.Diagnostic (AvoidAssemblyLocationInSingleFile.IL3000).WithSpan (8, 13, 8, 23).WithArguments ("System.Reflection.Assembly.Location"), + // (9,13): warning IL3001: Assemblies embedded in a single-file app cannot have additional files in the manifest. + VerifyCS.Diagnostic (AvoidAssemblyLocationInSingleFile.IL3001).WithSpan (9, 13, 9, 25).WithArguments ("System.Reflection.Assembly.GetFiles()") + ); + } + + [Fact] + public Task PublishSingleFileIsNotSet () + { + var src = @" +using System.Reflection; +class C +{ + public void M() + { + var a = Assembly.GetExecutingAssembly().Location; + } +}"; + // If 'PublishSingleFile' is not set to true, no diagnostics should be produced by the analyzer. This will + // effectively verify that the number of produced diagnostics matches the number of expected ones (zero). + return VerifyCS.VerifyAnalyzerAsync (src); + } + } +} diff --git a/test/ILLink.RoslynAnalyzer.Tests/LinkerTestCases.cs b/test/ILLink.RoslynAnalyzer.Tests/LinkerTestCases.cs index e63d2c02f4e0..f205fb25e38f 100644 --- a/test/ILLink.RoslynAnalyzer.Tests/LinkerTestCases.cs +++ b/test/ILLink.RoslynAnalyzer.Tests/LinkerTestCases.cs @@ -3,8 +3,6 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; -using System.Collections.Immutable; -using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Xunit; @@ -24,7 +22,7 @@ public void RequiresCapability (MethodDeclarationSyntax m, List case "MethodWithDuplicateRequiresAttribute": return; } - RunTest (m, attrs); + RunTest (m, attrs, UseMSBuildProperties (MSBuildPropertyOptionNames.PublishTrimmed)); } } } diff --git a/test/ILLink.RoslynAnalyzer.Tests/AnalyzerTests.cs b/test/ILLink.RoslynAnalyzer.Tests/RequiresUnreferencedCodeAnalyzerTests.cs similarity index 82% rename from test/ILLink.RoslynAnalyzer.Tests/AnalyzerTests.cs rename to test/ILLink.RoslynAnalyzer.Tests/RequiresUnreferencedCodeAnalyzerTests.cs index fc1ee182dcd9..81899e8906d0 100644 --- a/test/ILLink.RoslynAnalyzer.Tests/AnalyzerTests.cs +++ b/test/ILLink.RoslynAnalyzer.Tests/RequiresUnreferencedCodeAnalyzerTests.cs @@ -3,14 +3,22 @@ // See the LICENSE file in the project root for more information. using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Testing; using Xunit; using VerifyCS = ILLink.RoslynAnalyzer.Tests.CSharpAnalyzerVerifier< ILLink.RoslynAnalyzer.RequiresUnreferencedCodeAnalyzer>; namespace ILLink.RoslynAnalyzer.Tests { - public class AnalyzerTests + public class RequiresUnreferencedCodeAnalyzerTests { + static Task VerifyRequiresUnreferencedCodeAnalyzer (string source, params DiagnosticResult[] expected) + { + return VerifyCS.VerifyAnalyzerAsync (source, + TestCaseUtils.UseMSBuildProperties (MSBuildPropertyOptionNames.PublishTrimmed), + expected); + } + [Fact] public Task SimpleDiagnostic () { @@ -23,10 +31,9 @@ class C int M1() => 0; int M2() => M1(); }"; - return VerifyCS.VerifyAnalyzerAsync (TestRequiresWithMessageOnlyOnMethod, + return VerifyRequiresUnreferencedCodeAnalyzer (TestRequiresWithMessageOnlyOnMethod, // (8,17): warning IL2026: Calling 'System.Int32 C::M1()' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. message. - VerifyCS.Diagnostic ().WithSpan (8, 17, 8, 21).WithArguments ("C.M1()", "message", "") - ); + VerifyCS.Diagnostic ().WithSpan (8, 17, 8, 21).WithArguments ("C.M1()", "message", "")); } [Fact] @@ -46,7 +53,7 @@ static void RequiresWithMessageAndUrl () { } }"; - return VerifyCS.VerifyAnalyzerAsync (MessageAndUrlOnMethod, + return VerifyRequiresUnreferencedCodeAnalyzer (MessageAndUrlOnMethod, // (8,3): warning IL2026: Calling 'C.RequiresWithMessageAndUrl()' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. Message for --RequiresWithMessageAndUrl--. VerifyCS.Diagnostic ().WithSpan (8, 3, 8, 31).WithArguments ("C.RequiresWithMessageAndUrl()", "Message for --RequiresWithMessageAndUrl--", "https://helpurl") ); @@ -70,7 +77,7 @@ static void TestRequiresOnPropertyGetter () get { return 42; } } }"; - return VerifyCS.VerifyAnalyzerAsync (PropertyRequires, + return VerifyRequiresUnreferencedCodeAnalyzer (PropertyRequires, // (8,7): warning IL2026: Calling 'C.PropertyRequires.get' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. Message for --getter PropertyRequires--. VerifyCS.Diagnostic ().WithSpan (8, 7, 8, 23).WithArguments ("C.PropertyRequires.get", "Message for --getter PropertyRequires--", "") ); @@ -94,7 +101,7 @@ static void TestRequiresOnPropertySetter () set { } } }"; - return VerifyCS.VerifyAnalyzerAsync (PropertyRequires, + return VerifyRequiresUnreferencedCodeAnalyzer (PropertyRequires, // (8,3): warning IL2026: Calling 'C.PropertyRequires.set' which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. Message for --setter PropertyRequires--. VerifyCS.Diagnostic ().WithSpan (8, 3, 8, 19).WithArguments ("C.PropertyRequires.set", "Message for --setter PropertyRequires--", "") ); diff --git a/test/ILLink.RoslynAnalyzer.Tests/TestCaseUtils.cs b/test/ILLink.RoslynAnalyzer.Tests/TestCaseUtils.cs index 003823c02955..c62c2d762004 100644 --- a/test/ILLink.RoslynAnalyzer.Tests/TestCaseUtils.cs +++ b/test/ILLink.RoslynAnalyzer.Tests/TestCaseUtils.cs @@ -53,9 +53,9 @@ static bool IsWellKnown (AttributeSyntax attr) } } - internal static void RunTest (MethodDeclarationSyntax m, List attrs) + internal static void RunTest (MethodDeclarationSyntax m, List attrs, params (string, string)[] MSBuildProperties) { - var comp = CSharpAnalyzerVerifier.CreateCompilation (m.SyntaxTree).Result; + var comp = CSharpAnalyzerVerifier.CreateCompilation (m.SyntaxTree, MSBuildProperties).Result; var diags = comp.GetAnalyzerDiagnosticsAsync ().Result; var filtered = diags.Where (d => d.Location.SourceSpan.IntersectsWith (m.Span)) @@ -181,6 +181,11 @@ private static IEnumerable GetTestFiles () } } + internal static (string, string)[] UseMSBuildProperties (params string[] MSBuildProperties) + { + return MSBuildProperties.Select (msbp => ($"build_property.{msbp}", "true")).ToArray (); + } + internal static void GetDirectoryPaths (out string rootSourceDirectory, out string testAssemblyPath, [CallerFilePath] string thisFile = null) { diff --git a/test/ILLink.RoslynAnalyzer.Tests/Verifiers/CSharpAnalyzerVerifier`1.cs b/test/ILLink.RoslynAnalyzer.Tests/Verifiers/CSharpAnalyzerVerifier`1.cs index 91b28b1aaed1..4d96f37d89fd 100644 --- a/test/ILLink.RoslynAnalyzer.Tests/Verifiers/CSharpAnalyzerVerifier`1.cs +++ b/test/ILLink.RoslynAnalyzer.Tests/Verifiers/CSharpAnalyzerVerifier`1.cs @@ -71,9 +71,9 @@ public static DiagnosticResult Diagnostic (DiagnosticDescriptor descriptor) } /// - public static async Task VerifyAnalyzerAsync (string src, params DiagnosticResult[] expected) + public static async Task VerifyAnalyzerAsync (string src, (string, string)[]? analyzerOptions = null, params DiagnosticResult[] expected) { - var diags = await (await CreateCompilation (src)).GetAllDiagnosticsAsync (); + var diags = await (await CreateCompilation (src, analyzerOptions)).GetAllDiagnosticsAsync (); var analyzers = ImmutableArray.Create (new TAnalyzer ()); VerifyDiagnosticResults (diags, analyzers, expected, DefaultVerifier);