From 3c8e158175b1a6efdc3ad8638cec268b85577835 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Mon, 1 Nov 2021 13:57:29 -0500 Subject: [PATCH] [compatibility] IsTrimmable support, fix linker warnings (#3161) To make `Microsoft.Maui.Controls.Compatibility.dll` "trimmable", we can simply add: [assembly: AssemblyMetadata ("IsTrimmable", "True")] This doesn't mean it actually *works* though! We should enable linker warnings and fix what it finds. To view linker warnings in .NET 6, you can do: > .\bin\dotnet\dotnet.exe build ` .\src\Controls\samples\Controls.Sample.SingleProject\Maui.Controls.Sample.SingleProject.csproj -f net6.0-android ` -c Release ` -p:SuppressTrimAnalysisWarnings=false ` -p:TrimmerSingleWarn=false ` -bl Then open the `msbuild.binlog` file and filter the warnings (that get upgraded to errors) for `src/Compatibility`: src\Compatibility\Core\src\Android\Deserializer.cs(38,6): error IL2026: Microsoft.Maui.Controls.Compatibility.Platform.Android.Deserializer.<>c.b__2_0(): Using member 'System.Runtime.Serialization.XmlObjectSerializer.ReadObject(XmlDictionaryReader)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. Data Contract Serialization and Deserialization might require types that cannot be statically analyzed. Make sure all of the required types are preserved. src\Compatibility\Core\src\Android\Deserializer.cs(71,6): error IL2026: Microsoft.Maui.Controls.Compatibility.Platform.Android.Deserializer.<>c__DisplayClass3_0.b__0(): Using member 'System.Runtime.Serialization.XmlObjectSerializer.WriteObject(XmlDictionaryWriter,Object)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. Data Contract Serialization and Deserialization might require types that cannot be statically analyzed. Make sure all of the required types are preserved. src\Compatibility\Core\src\Android\AndroidAppIndexProvider.cs(14,5): error IL2072: Microsoft.Maui.Controls.Compatibility.Platform.Android.AndroidAppIndexProvider.AndroidAppIndexProvider(Context): '#0' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicConstructors' in call to 'System.Object System.Activator::CreateInstance(System.Type,System.Object[],System.Object[])'. The return value of method 'System.Type.GetType(String,Boolean)' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to. src\Compatibility\Core\src\AppHostBuilderExtensions.cs(49,4): error IL2091: Microsoft.Maui.Controls.Hosting.MauiAppBuilderExtensions.UseMauiApp(MauiAppBuilder): 'TImplementation' generic argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicConstructors' in 'Microsoft.Extensions.DependencyInjection.Extensions.ServiceCollectionDescriptorExtensions.TryAddSingleton(IServiceCollection)'. The generic parameter 'TApp' of 'Microsoft.Maui.Controls.Hosting.MauiAppBuilderExtensions.UseMauiApp(MauiAppBuilder)' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to. src\Compatibility\Core\src\Android\NativeBindingservice.cs(16,4): error IL2075: Microsoft.Maui.Controls.Compatibility.Platform.Android.NativeBindingService.TrySetBinding(Object,String,BindingBase): 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicProperties' in call to 'System.Type.GetProperty(String)'. The return value of method 'System.Type System.Object::GetType()' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to. src\Compatibility\Core\src\Android\ResourceManager.cs(28,4): error IL2026: Microsoft.Maui.Controls.Compatibility.Platform.Android.ResourceManager.FindType(String,String): Using member 'System.Reflection.Assembly.GetTypes()' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. Types might be removed. src\Compatibility\Core\src\Android\ResourceManager.cs(419,4): error IL2070: Microsoft.Maui.Controls.Compatibility.Platform.Android.ResourceManager.GetId(Type,String): 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicFields' in call to 'System.Type.GetFields()'. The parameter 'type' of method 'Microsoft.Maui.Controls.Compatibility.Platform.Android.ResourceManager.GetId(Type,String)' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to. src\Compatibility\Core\src\Android\ResourceManager.cs(432,5): error IL2070: Microsoft.Maui.Controls.Compatibility.Platform.Android.ResourceManager.GetId(Type,String): 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicProperties' in call to 'System.Type.GetProperties()'. The parameter 'type' of method 'Microsoft.Maui.Controls.Compatibility.Platform.Android.ResourceManager.GetId(Type,String)' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to. I went through applying the new linker attributes to resolve these issues in this assembly. In most cases, I simply added C# attributes to solve warnings. However, a couple places needed code changes: * `Deserializer`, I removed usage of a lambda for a regular method. There isn't a way to decorate lambdas with C# attributes to appease the linker. * `ResourceManager.FindType()`, I replaced System.Linq usage with a `foreach` loop that the linker could better understand (lambdas again!). This should generally improve performance, anyway. Lastly, I had to actually add some attributes to `DependencyService`, otherwise we got crashes at startup like: android.runtime.JavaProxyThrowable: System.MissingMethodException: Arg_NoDefCTor, Microsoft.Maui.Controls.Compatibility.Platform.Android.NativeValueConverterService at System.RuntimeType.CreateInstanceMono(Boolean , Boolean ) at System.RuntimeType.CreateInstanceDefaultCtor(Boolean , Boolean ) at System.Activator.CreateInstance(Type , Boolean , Boolean ) at System.Activator.CreateInstance(Type , Boolean ) at System.Activator.CreateInstance(Type ) at Microsoft.Maui.Controls.DependencyService.Get[INativeValueConverterService](DependencyFetchTarget fetchTarget) at Microsoft.Maui.Controls.Xaml.TypeConversionExtensions.ConvertTo(Object value, Type toType, Func`1 getConverter, IServiceProvider serviceProvider, Exception& exception) at Microsoft.Maui.Controls.Xaml.TypeConversionExtensions.ConvertTo(Object value, Type toType, Func`1 minfoRetriever, IServiceProvider serviceProvider, Exception& exception) at Microsoft.Maui.Controls.Xaml.ValueConverterProvider.Convert(Object value, Type toType, Func`1 minfoRetriever, IServiceProvider serviceProvider) at Microsoft.Maui.Controls.Xaml.AppThemeBindingExtension.Microsoft.Maui.Controls.Xaml.IMarkupExtension.ProvideValue(IServiceProvider serviceProvider) at Maui.Controls.Sample.XamlApp.InitializeComponent() ~~ Results ~~ I built `Maui.Controls.Sample.SingleProject.csproj` with `-p:AndroidUseAssemblyStore=false`, so we can see the size differences of individual assemblies. * Before 30275786 bytes * After 30181399 bytes > apkdiff -f before.apk after.apk Size difference in bytes ([*1] apk1 only, [*2] apk2 only): + 437 assemblies/Microsoft.Maui.Controls.dll + 161 assemblies/x86/System.Private.CoreLib.dll + 160 assemblies/arm64-v8a/System.Private.CoreLib.dll + 139 assemblies/armeabi-v7a/System.Private.CoreLib.dll + 127 assemblies/x86_64/System.Private.CoreLib.dll + 28 assemblies/System.Runtime.dll + 1 assemblies/Maui.Controls.Sample.SingleProject.dll - 1 assemblies/Microsoft.Maui.Controls.Xaml.dll - 15 assemblies/System.Xml.ReaderWriter.dll - 16 assemblies/System.Threading.dll - 231 META-INF/BNDLTOOL.SF - 231 META-INF/MANIFEST.MF - 2,498 assemblies/System.Runtime.Serialization.Xml.dll *1 - 3,523 assemblies/System.IO.IsolatedStorage.dll *1 - 6,341 assemblies/Mono.Android.dll - 8,352 lib/armeabi-v7a/libxamarin-app.so - 8,352 lib/x86/libxamarin-app.so - 8,408 lib/arm64-v8a/libxamarin-app.so - 8,408 lib/x86_64/libxamarin-app.so - 38,104 classes.dex - 72,810 assemblies/Microsoft.Maui.Controls.Compatibility.dll Summary: - 462 Other entries -0.00% (of 12,105,588) - 84,151 Assemblies -0.82% (of 10,293,792) - 38,104 Dalvik executables -0.59% (of 6,486,156) - 33,520 Shared libraries -0.18% (of 18,486,516) - 183,296 Uncompressed assemblies -0.81% (of 22,604,792) - 94,387 Package size difference -0.31% (of 30,275,786) An average of 10 runs on a Pixel 5, seems to show a difference in startup as well: Before: Activity: Displayed 1.676s After: Activity: Displayed 1.642s I think this might help startup by ~34ms? --- .../src/Android/AndroidAppIndexProvider.cs | 4 + .../Core/src/Android/Deserializer.cs | 87 ++++++------ .../Core/src/Android/NativeBindingservice.cs | 2 + .../Core/src/Android/ResourceManager.cs | 34 ++++- .../Core/src/AppHostBuilderExtensions.cs | 5 +- .../Core/src/GTK/GtkSerializer.cs | 3 + .../Core/src/Properties/AssemblyInfo.cs | 2 + .../Core/src/Tizen/Deserializer.cs | 113 ++++++++------- .../Core/src/Tizen/NativeBindingService.cs | 2 + .../Core/src/WPF/Deserializer.cs | 3 + .../Core/src/Windows/NativeBindingService.cs | 2 + .../Core/src/Windows/WindowsSerializer.cs | 86 ++++++------ .../Core/src/iOS/Deserializer.cs | 89 ++++++------ .../Core/src/iOS/NativeBindingService.cs | 2 + src/Controls/src/Core/DependencyService.cs | 9 +- src/Controls/src/Core/IDeserializer.cs | 3 + src/Controls/src/Core/TrimmerConstants.cs | 9 ++ .../DynamicDependencyAttribute.cs | 131 ++++++++++++++++++ .../DynamicallyAccessedMemberTypes.cs | 91 ++++++++++++ .../DynamicallyAccessedMembersAttribute.cs | 57 ++++++++ .../RequiresUnreferencedCodeAttribute.cs | 48 +++++++ .../UnconditionalSuppressMessageAttribute.cs | 88 ++++++++++++ 22 files changed, 682 insertions(+), 188 deletions(-) create mode 100644 src/Controls/src/Core/TrimmerConstants.cs create mode 100644 src/Core/src/System.Diagnostics.CodeAnalysis/DynamicDependencyAttribute.cs create mode 100644 src/Core/src/System.Diagnostics.CodeAnalysis/DynamicallyAccessedMemberTypes.cs create mode 100644 src/Core/src/System.Diagnostics.CodeAnalysis/DynamicallyAccessedMembersAttribute.cs create mode 100644 src/Core/src/System.Diagnostics.CodeAnalysis/RequiresUnreferencedCodeAttribute.cs create mode 100644 src/Core/src/System.Diagnostics.CodeAnalysis/UnconditionalSuppressMessageAttribute.cs diff --git a/src/Compatibility/Core/src/Android/AndroidAppIndexProvider.cs b/src/Compatibility/Core/src/Android/AndroidAppIndexProvider.cs index 47de56645ba4..1ae6318d2790 100644 --- a/src/Compatibility/Core/src/Android/AndroidAppIndexProvider.cs +++ b/src/Compatibility/Core/src/Android/AndroidAppIndexProvider.cs @@ -1,10 +1,14 @@ using System; +using System.Diagnostics.CodeAnalysis; using Android.Content; namespace Microsoft.Maui.Controls.Compatibility.Platform.Android { public class AndroidAppIndexProvider : IAppIndexingProvider { + [UnconditionalSuppressMessage ("Trimming", "IL2035", Justification = AppLinksAssemblyName + ".dll is not always present.")] + [UnconditionalSuppressMessage ("Trimming", "IL2072", Justification = AppLinksAssemblyName + ".dll is not always present.")] + [DynamicDependency (DynamicallyAccessedMemberTypes.PublicConstructors, AppLinksAssemblyName + "." + AppLinksClassName, AppLinksAssemblyName)] public AndroidAppIndexProvider(Context context) { var fullyQualifiedName = $"{AppLinksAssemblyName}.{AppLinksClassName}, {AppLinksAssemblyName}"; diff --git a/src/Compatibility/Core/src/Android/Deserializer.cs b/src/Compatibility/Core/src/Android/Deserializer.cs index 63959b272a54..24413027a46f 100644 --- a/src/Compatibility/Core/src/Android/Deserializer.cs +++ b/src/Compatibility/Core/src/Android/Deserializer.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO; -using System.IO.IsolatedStorage; using System.Runtime.Serialization; using System.Threading.Tasks; using System.Xml; @@ -17,36 +17,38 @@ internal class Deserializer : IDeserializer static string GetFilePath() => Path.Combine(Essentials.FileSystem.CacheDirectory, PropertyStoreFile); - public Task> DeserializePropertiesAsync() + [RequiresUnreferencedCode(TrimmerConstants.SerializerTrimmerWarning)] + public Task> DeserializePropertiesAsync() => Task.Factory.StartNew(DeserializeProperties); + + [RequiresUnreferencedCode(TrimmerConstants.SerializerTrimmerWarning)] + IDictionary DeserializeProperties() { // Deserialize property dictionary to local storage // Make sure to use Internal - return Task.Run(() => - { - var path = GetFilePath(); + var path = GetFilePath(); - if (!File.Exists(path)) - return null; + if (!File.Exists(path)) + return null; - using var stream = File.OpenRead(path); - using var xmlReader = XmlReader.Create(stream); - using var reader = XmlDictionaryReader.CreateDictionaryReader(xmlReader); + using var stream = File.OpenRead(path); + using var xmlReader = XmlReader.Create(stream); + using var reader = XmlDictionaryReader.CreateDictionaryReader(xmlReader); - try - { - var dcs = new DataContractSerializer(typeof(Dictionary)); - return (IDictionary)dcs.ReadObject(reader); - } - catch (Exception e) - { - Debug.WriteLine("Could not deserialize properties: " + e.Message); - Log.Warning("Microsoft.Maui.Controls.Compatibility PropertyStore", $"Exception while reading Application properties: {e}"); - } + try + { + var dcs = new DataContractSerializer(typeof(Dictionary)); + return (IDictionary)dcs.ReadObject(reader); + } + catch (Exception e) + { + Debug.WriteLine("Could not deserialize properties: " + e.Message); + Log.Warning("Microsoft.Maui.Controls.Compatibility PropertyStore", $"Exception while reading Application properties: {e}"); + } - return null; - }); + return null; } + [RequiresUnreferencedCode(TrimmerConstants.SerializerTrimmerWarning)] public Task SerializePropertiesAsync(IDictionary properties) { properties = new Dictionary(properties); @@ -55,29 +57,32 @@ public Task SerializePropertiesAsync(IDictionary properties) if (properties.Count <= 0) return Task.CompletedTask; + return Task.Factory.StartNew (SerializeProperties, properties); + } + + [RequiresUnreferencedCode(TrimmerConstants.SerializerTrimmerWarning)] + void SerializeProperties(object properties) + { // Serialize property dictionary to local storage // Make sure to use Internal - return Task.Run(() => - { - var path = GetFilePath(); + var path = GetFilePath(); - using var stream = File.Create(path); - using var xmlWriter = XmlWriter.Create(stream); - using var writer = XmlDictionaryWriter.CreateDictionaryWriter(xmlWriter); + using var stream = File.Create(path); + using var xmlWriter = XmlWriter.Create(stream); + using var writer = XmlDictionaryWriter.CreateDictionaryWriter(xmlWriter); - try - { - var dcs = new DataContractSerializer(typeof(Dictionary)); - dcs.WriteObject(writer, properties); - writer.Flush(); - } - catch (Exception e) - { - Debug.WriteLine("Could not serialize properties: " + e.Message); - Log.Warning("Microsoft.Maui.Controls.Compatibility PropertyStore", $"Exception while writing Application properties: {e}"); - return; - } - }); + try + { + var dcs = new DataContractSerializer(typeof(Dictionary)); + dcs.WriteObject(writer, properties); + writer.Flush(); + } + catch (Exception e) + { + Debug.WriteLine("Could not serialize properties: " + e.Message); + Log.Warning("Microsoft.Maui.Controls.Compatibility PropertyStore", $"Exception while writing Application properties: {e}"); + return; + } } } } \ No newline at end of file diff --git a/src/Compatibility/Core/src/Android/NativeBindingservice.cs b/src/Compatibility/Core/src/Android/NativeBindingservice.cs index 1133112bd7f6..2ed8523c618b 100644 --- a/src/Compatibility/Core/src/Android/NativeBindingservice.cs +++ b/src/Compatibility/Core/src/Android/NativeBindingservice.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using Microsoft.Maui.Controls.Xaml.Internals; using AView = Android.Views.View; @@ -8,6 +9,7 @@ namespace Microsoft.Maui.Controls.Compatibility.Platform.Android { class NativeBindingService : INativeBindingService { + [UnconditionalSuppressMessage ("Trimming", "IL2075", Justification = TrimmerConstants.NativeBindingService)] public bool TrySetBinding(object target, string propertyName, BindingBase binding) { var view = target as AView; diff --git a/src/Compatibility/Core/src/Android/ResourceManager.cs b/src/Compatibility/Core/src/Android/ResourceManager.cs index 00792751c915..8466d717410b 100644 --- a/src/Compatibility/Core/src/Android/ResourceManager.cs +++ b/src/Compatibility/Core/src/Android/ResourceManager.cs @@ -1,7 +1,7 @@ using System; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.IO; -using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; @@ -23,23 +23,39 @@ public static class ResourceManager static ImageCache GetCache() => _lruCache.Value; static Assembly _assembly; + [UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = "Resource.designer.cs is in the root application assembly, which should be preserved.")] + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] static Type FindType(string name, string altName) { - return _assembly?.GetTypes().FirstOrDefault(x => x.Name == name || x.Name == altName); + if (_assembly != null) + { + foreach (var type in _assembly.GetTypes()) + { + if (type.Name == name || type.Name == altName) + return type; + } + } + return null; } + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] static Type _drawableClass; + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] static Type _resourceClass; + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] static Type _styleClass; + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] static Type _layoutClass; public static Type DrawableClass { + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] get { if (_drawableClass == null) _drawableClass = FindType("Drawable", "Resource_Drawable"); return _drawableClass; } + [param: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] set { _drawableClass = value; @@ -48,12 +64,14 @@ public static Type DrawableClass public static Type ResourceClass { + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] get { if (_resourceClass == null) _resourceClass = FindType("Id", "Resource_Id"); return _resourceClass; } + [param: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] set { _resourceClass = value; @@ -62,12 +80,14 @@ public static Type ResourceClass public static Type StyleClass { + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] get { if (_styleClass == null) _styleClass = FindType("Style", "Resource_Style"); return _styleClass; } + [param: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] set { _styleClass = value; @@ -76,12 +96,14 @@ public static Type StyleClass public static Type LayoutClass { + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] get { if (_layoutClass == null) _layoutClass = FindType("Layout", "Resource_Layout"); return _layoutClass; } + [param: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] set { _layoutClass = value; @@ -359,17 +381,17 @@ public static void Init(Assembly mainAssembly) _assembly = mainAssembly; } - static int IdFromTitle(string title, Type resourceType, string defType, Resources resource) + static int IdFromTitle(string title, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] Type resourceType, string defType, Resources resource) { return IdFromTitle(title, resourceType, defType, resource, Platform.GetPackageName()); } - static int IdFromTitle(string title, Type resourceType, string defType, Context context) + static int IdFromTitle(string title, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] Type resourceType, string defType, Context context) { return IdFromTitle(title, resourceType, defType, context?.Resources, context?.PackageName); } - static int IdFromTitle(string title, Type resourceType, string defType, Resources resource, string packageName) + static int IdFromTitle(string title, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] Type resourceType, string defType, Resources resource, string packageName) { int id = 0; if (title == null) @@ -409,7 +431,7 @@ int SearchByIdentifier(string n, string d, Resources r, string p) return GetId(resourceType, name); } - static int GetId(Type type, string memberName) + static int GetId([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] Type type, string memberName) { // This may legitimately be null in designer scenarios if (type == null) diff --git a/src/Compatibility/Core/src/AppHostBuilderExtensions.cs b/src/Compatibility/Core/src/AppHostBuilderExtensions.cs index 409eef83d2e1..0e0a18460817 100644 --- a/src/Compatibility/Core/src/AppHostBuilderExtensions.cs +++ b/src/Compatibility/Core/src/AppHostBuilderExtensions.cs @@ -1,5 +1,6 @@ #nullable enable using System; +using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting; @@ -43,7 +44,7 @@ namespace Microsoft.Maui.Controls.Hosting { public static class MauiAppBuilderExtensions { - public static MauiAppBuilder UseMauiApp(this MauiAppBuilder builder) + public static MauiAppBuilder UseMauiApp<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TApp>(this MauiAppBuilder builder) where TApp : class, IApplication { builder.Services.TryAddSingleton(); @@ -51,7 +52,7 @@ public static MauiAppBuilder UseMauiApp(this MauiAppBuilder builder) return builder; } - public static MauiAppBuilder UseMauiApp(this MauiAppBuilder builder, Func implementationFactory) + public static MauiAppBuilder UseMauiApp<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TApp>(this MauiAppBuilder builder, Func implementationFactory) where TApp : class, IApplication { builder.Services.TryAddSingleton(implementationFactory); diff --git a/src/Compatibility/Core/src/GTK/GtkSerializer.cs b/src/Compatibility/Core/src/GTK/GtkSerializer.cs index 11d5dfef3b1e..92964cddd245 100644 --- a/src/Compatibility/Core/src/GTK/GtkSerializer.cs +++ b/src/Compatibility/Core/src/GTK/GtkSerializer.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.IO.IsolatedStorage; using System.Runtime.Serialization; @@ -13,6 +14,7 @@ internal sealed class GtkSerializer : IDeserializer { const string PropertyStoreFile = "PropertyStore.forms"; + [RequiresUnreferencedCode(TrimmerConstants.SerializerTrimmerWarning)] public Task> DeserializePropertiesAsync() { try @@ -54,6 +56,7 @@ public Task> DeserializePropertiesAsync() } } + [RequiresUnreferencedCode(TrimmerConstants.SerializerTrimmerWarning)] public Task SerializePropertiesAsync(IDictionary properties) { try diff --git a/src/Compatibility/Core/src/Properties/AssemblyInfo.cs b/src/Compatibility/Core/src/Properties/AssemblyInfo.cs index a1851c4d4bcb..b8068f876c3a 100644 --- a/src/Compatibility/Core/src/Properties/AssemblyInfo.cs +++ b/src/Compatibility/Core/src/Properties/AssemblyInfo.cs @@ -1,4 +1,6 @@ +using System.Reflection; using System.Runtime.CompilerServices; +[assembly: AssemblyMetadata ("IsTrimmable", "True")] [assembly: InternalsVisibleTo("Compatibility.Windows.UnitTests")] [assembly: InternalsVisibleTo("Compatibility.Android.UnitTests")] \ No newline at end of file diff --git a/src/Compatibility/Core/src/Tizen/Deserializer.cs b/src/Compatibility/Core/src/Tizen/Deserializer.cs index 4dc8ef283cc3..a8e7a690ab31 100644 --- a/src/Compatibility/Core/src/Tizen/Deserializer.cs +++ b/src/Compatibility/Core/src/Tizen/Deserializer.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO.IsolatedStorage; using System.Runtime.Serialization; using System.Threading.Tasks; @@ -12,85 +13,91 @@ internal class Deserializer : Internals.IDeserializer { const string PropertyStoreFile = "PropertyStore.forms"; - public Task> DeserializePropertiesAsync() + public Task> DeserializePropertiesAsync() => Task.Factory.StartNew(DeserializeProperties); + + [RequiresUnreferencedCode(TrimmerConstants.SerializerTrimmerWarning)] + IDictionary DeserializeProperties() { // Deserialize property dictionary to local storage // Make sure to use Internal - return Task.Run(() => + using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication()) { - using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication()) + if (!store.FileExists(PropertyStoreFile)) + return null; + + using (IsolatedStorageFileStream stream = store.OpenFile(PropertyStoreFile, System.IO.FileMode.Open, System.IO.FileAccess.Read)) + using (XmlDictionaryReader reader = XmlDictionaryReader.CreateBinaryReader(stream, XmlDictionaryReaderQuotas.Max)) { - if (!store.FileExists(PropertyStoreFile)) + if (stream.Length == 0) return null; - using (IsolatedStorageFileStream stream = store.OpenFile(PropertyStoreFile, System.IO.FileMode.Open, System.IO.FileAccess.Read)) - using (XmlDictionaryReader reader = XmlDictionaryReader.CreateBinaryReader(stream, XmlDictionaryReaderQuotas.Max)) + try { - if (stream.Length == 0) - return null; - - try - { - var dcs = new DataContractSerializer(typeof(Dictionary)); - return (IDictionary)dcs.ReadObject(reader); - } - catch (Exception e) - { - Debug.WriteLine("Could not deserialize properties: " + e.Message); - Internals.Log.Warning("Microsoft.Maui.Controls.Compatibility PropertyStore", $"Exception while reading Application properties: {e}"); - } + var dcs = new DataContractSerializer(typeof(Dictionary)); + return (IDictionary)dcs.ReadObject(reader); + } + catch (Exception e) + { + Debug.WriteLine("Could not deserialize properties: " + e.Message); + Internals.Log.Warning("Microsoft.Maui.Controls.Compatibility PropertyStore", $"Exception while reading Application properties: {e}"); } - } + } - return null; - }); + return null; } + [RequiresUnreferencedCode(TrimmerConstants.SerializerTrimmerWarning)] public Task SerializePropertiesAsync(IDictionary properties) { properties = new Dictionary(properties); - // Serialize property dictionary to local storage - // Make sure to use Internal - return Task.Run(() => + + // No need to write 0 properties if no file exists + if (properties.Count <= 0) + return Task.CompletedTask; + + return Task.Factory.StartNew(SerializeProperties, properties); + } + + [RequiresUnreferencedCode(TrimmerConstants.SerializerTrimmerWarning)] + void SerializeProperties(object properties) + { + using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication()) { - using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication()) + // No need to write 0 properties if no file exists + if (properties.Count == 0 && !store.FileExists(PropertyStoreFile)) + { + return; + } + using (IsolatedStorageFileStream stream = store.OpenFile(PropertyStoreFile + ".tmp", System.IO.FileMode.OpenOrCreate)) + using (XmlDictionaryWriter writer = XmlDictionaryWriter.CreateBinaryWriter(stream)) { - // No need to write 0 properties if no file exists - if (properties.Count == 0 && !store.FileExists(PropertyStoreFile)) - { - return; - } - using (IsolatedStorageFileStream stream = store.OpenFile(PropertyStoreFile + ".tmp", System.IO.FileMode.OpenOrCreate)) - using (XmlDictionaryWriter writer = XmlDictionaryWriter.CreateBinaryWriter(stream)) - { - try - { - var dcs = new DataContractSerializer(typeof(Dictionary)); - dcs.WriteObject(writer, properties); - writer.Flush(); - } - catch (Exception e) - { - Debug.WriteLine("Could not serialize properties: " + e.Message); - Internals.Log.Warning("Microsoft.Maui.Controls.Compatibility PropertyStore", $"Exception while writing Application properties: {e}"); - return; - } - } - try { - if (store.FileExists(PropertyStoreFile)) - store.DeleteFile(PropertyStoreFile); - store.MoveFile(PropertyStoreFile + ".tmp", PropertyStoreFile); + var dcs = new DataContractSerializer(typeof(Dictionary)); + dcs.WriteObject(writer, properties); + writer.Flush(); } catch (Exception e) { - Debug.WriteLine("Could not move new serialized property file over old: " + e.Message); + Debug.WriteLine("Could not serialize properties: " + e.Message); Internals.Log.Warning("Microsoft.Maui.Controls.Compatibility PropertyStore", $"Exception while writing Application properties: {e}"); + return; } } - }); + + try + { + if (store.FileExists(PropertyStoreFile)) + store.DeleteFile(PropertyStoreFile); + store.MoveFile(PropertyStoreFile + ".tmp", PropertyStoreFile); + } + catch (Exception e) + { + Debug.WriteLine("Could not move new serialized property file over old: " + e.Message); + Internals.Log.Warning("Microsoft.Maui.Controls.Compatibility PropertyStore", $"Exception while writing Application properties: {e}"); + } + } } } } \ No newline at end of file diff --git a/src/Compatibility/Core/src/Tizen/NativeBindingService.cs b/src/Compatibility/Core/src/Tizen/NativeBindingService.cs index a4a064c83d33..adfc61627ccf 100644 --- a/src/Compatibility/Core/src/Tizen/NativeBindingService.cs +++ b/src/Compatibility/Core/src/Tizen/NativeBindingService.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using Microsoft.Maui.Controls.Compatibility.Internals; using Microsoft.Maui.Controls.Compatibility.Xaml.Internals; @@ -7,6 +8,7 @@ namespace Microsoft.Maui.Controls.Compatibility.Platform.Tizen { class NativeBindingService : INativeBindingService { + [UnconditionalSuppressMessage ("Trimming", "IL2075", Justification = TrimmerConstants.NativeBindingService)] public bool TrySetBinding(object target, string propertyName, BindingBase binding) { var view = target as EObject; diff --git a/src/Compatibility/Core/src/WPF/Deserializer.cs b/src/Compatibility/Core/src/WPF/Deserializer.cs index 3131bcc87575..9a4153e3373d 100644 --- a/src/Compatibility/Core/src/WPF/Deserializer.cs +++ b/src/Compatibility/Core/src/WPF/Deserializer.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.IO.IsolatedStorage; using System.Linq; @@ -15,6 +16,7 @@ internal sealed class Deserializer : IDeserializer { const string PropertyStoreFile = "PropertyStore.forms"; + [RequiresUnreferencedCode(TrimmerConstants.SerializerTrimmerWarning)] public Task> DeserializePropertiesAsync() { IsolatedStorageFile isoStore = IsolatedStorageFile.GetStore(IsolatedStorageScope.User | IsolatedStorageScope.Assembly, null, null); @@ -42,6 +44,7 @@ public Task> DeserializePropertiesAsync() } } + [RequiresUnreferencedCode(TrimmerConstants.SerializerTrimmerWarning)] public async Task SerializePropertiesAsync(IDictionary properties) { try diff --git a/src/Compatibility/Core/src/Windows/NativeBindingService.cs b/src/Compatibility/Core/src/Windows/NativeBindingService.cs index 068d2073892b..4bf901db4a51 100644 --- a/src/Compatibility/Core/src/Windows/NativeBindingService.cs +++ b/src/Compatibility/Core/src/Windows/NativeBindingService.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using System.Reflection; using Microsoft.UI.Xaml; using Microsoft.Maui.Controls.Xaml.Internals; @@ -8,6 +9,7 @@ namespace Microsoft.Maui.Controls.Compatibility.Platform.UWP { public class NativeBindingService : INativeBindingService { + [UnconditionalSuppressMessage ("Trimming", "IL2075", Justification = TrimmerConstants.NativeBindingService)] public bool TrySetBinding(object target, string propertyName, BindingBase binding) { var view = target as FrameworkElement; diff --git a/src/Compatibility/Core/src/Windows/WindowsSerializer.cs b/src/Compatibility/Core/src/Windows/WindowsSerializer.cs index 650fb8c3fc32..44ad6f2c94bf 100644 --- a/src/Compatibility/Core/src/Windows/WindowsSerializer.cs +++ b/src/Compatibility/Core/src/Windows/WindowsSerializer.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Runtime.Serialization; using System.Threading.Tasks; @@ -16,36 +17,38 @@ internal sealed class WindowsSerializer : IDeserializer static string GetFilePath() => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), PropertyStoreFile); - public Task> DeserializePropertiesAsync() + [RequiresUnreferencedCode(TrimmerConstants.SerializerTrimmerWarning)] + public Task> DeserializePropertiesAsync() => Task.Factory.StartNew(DeserializeProperties); + + [RequiresUnreferencedCode(TrimmerConstants.SerializerTrimmerWarning)] + IDictionary DeserializeProperties() { // Deserialize property dictionary to local storage // Make sure to use Internal - return Task.Run(() => - { - var path = GetFilePath(); + var path = GetFilePath(); - if (!File.Exists(path)) - return null; + if (!File.Exists(path)) + return null; - using var stream = File.OpenRead(path); - using var xmlReader = XmlReader.Create(stream); - using var reader = XmlDictionaryReader.CreateDictionaryReader(xmlReader); + using var stream = File.OpenRead(path); + using var xmlReader = XmlReader.Create(stream); + using var reader = XmlDictionaryReader.CreateDictionaryReader(xmlReader); - try - { - var dcs = new DataContractSerializer(typeof(Dictionary)); - return (IDictionary)dcs.ReadObject(reader); - } - catch (Exception e) - { - Debug.WriteLine("Could not deserialize properties: " + e.Message); - Log.Warning("Microsoft.Maui.Controls.Compatibility PropertyStore", $"Exception while reading Application properties: {e}"); - } + try + { + var dcs = new DataContractSerializer(typeof(Dictionary)); + return (IDictionary)dcs.ReadObject(reader); + } + catch (Exception e) + { + Debug.WriteLine("Could not deserialize properties: " + e.Message); + Log.Warning("Microsoft.Maui.Controls.Compatibility PropertyStore", $"Exception while reading Application properties: {e}"); + } - return null; - }); + return null; } + [RequiresUnreferencedCode(TrimmerConstants.SerializerTrimmerWarning)] public Task SerializePropertiesAsync(IDictionary properties) { properties = new Dictionary(properties); @@ -54,29 +57,32 @@ public Task SerializePropertiesAsync(IDictionary properties) if (properties.Count <= 0) return Task.CompletedTask; + return Task.Factory.StartNew(SerializeProperties, properties); + } + + [RequiresUnreferencedCode(TrimmerConstants.SerializerTrimmerWarning)] + void SerializeProperties(object properties) + { // Serialize property dictionary to local storage // Make sure to use Internal - return Task.Run(() => - { - var path = GetFilePath(); + var path = GetFilePath(); - using var stream = File.Create(path); - using var xmlWriter = XmlWriter.Create(stream); - using var writer = XmlDictionaryWriter.CreateDictionaryWriter(xmlWriter); + using var stream = File.Create(path); + using var xmlWriter = XmlWriter.Create(stream); + using var writer = XmlDictionaryWriter.CreateDictionaryWriter(xmlWriter); - try - { - var dcs = new DataContractSerializer(typeof(Dictionary)); - dcs.WriteObject(writer, properties); - writer.Flush(); - } - catch (Exception e) - { - Debug.WriteLine("Could not serialize properties: " + e.Message); - Log.Warning("Microsoft.Maui.Controls.Compatibility PropertyStore", $"Exception while writing Application properties: {e}"); - return; - } - }); + try + { + var dcs = new DataContractSerializer(typeof(Dictionary)); + dcs.WriteObject(writer, properties); + writer.Flush(); + } + catch (Exception e) + { + Debug.WriteLine("Could not serialize properties: " + e.Message); + Log.Warning("Microsoft.Maui.Controls.Compatibility PropertyStore", $"Exception while writing Application properties: {e}"); + return; + } } } } \ No newline at end of file diff --git a/src/Compatibility/Core/src/iOS/Deserializer.cs b/src/Compatibility/Core/src/iOS/Deserializer.cs index 0d3e76df160b..d14edd9227da 100644 --- a/src/Compatibility/Core/src/iOS/Deserializer.cs +++ b/src/Compatibility/Core/src/iOS/Deserializer.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO; -using System.IO.IsolatedStorage; using System.Runtime.Serialization; using System.Threading.Tasks; using System.Xml; @@ -36,35 +36,37 @@ void SaveSerialized(string str) ud.SetString(str, PropertyStoreKey); } - public Task> DeserializePropertiesAsync() + [RequiresUnreferencedCode(TrimmerConstants.SerializerTrimmerWarning)] + public Task> DeserializePropertiesAsync() => Task.Factory.StartNew(DeserializeProperties); + + [RequiresUnreferencedCode(TrimmerConstants.SerializerTrimmerWarning)] + IDictionary DeserializeProperties() { // Deserialize property dictionary to local storage // Make sure to use Internal - return Task.Run(() => - { - var str = LoadSerialized(); + var str = LoadSerialized(); - if (string.IsNullOrEmpty(str)) - return null; + if (string.IsNullOrEmpty(str)) + return null; - using var stringReader = new StringReader(str); - using var reader = XmlReader.Create(stringReader); + using var stringReader = new StringReader(str); + using var reader = XmlReader.Create(stringReader); - try - { - var dcs = new DataContractSerializer(typeof(Dictionary)); - return (IDictionary)dcs.ReadObject(reader); - } - catch (Exception e) - { - Debug.WriteLine("Could not deserialize properties: " + e.Message); - Log.Warning("Microsoft.Maui.Controls.Compatibility PropertyStore", $"Exception while reading Application properties: {e}"); - } + try + { + var dcs = new DataContractSerializer(typeof(Dictionary)); + return (IDictionary)dcs.ReadObject(reader); + } + catch (Exception e) + { + Debug.WriteLine("Could not deserialize properties: " + e.Message); + Log.Warning("Microsoft.Maui.Controls.Compatibility PropertyStore", $"Exception while reading Application properties: {e}"); + } - return null; - }); + return null; } + [RequiresUnreferencedCode(TrimmerConstants.SerializerTrimmerWarning)] public Task SerializePropertiesAsync(IDictionary properties) { properties = new Dictionary(properties); @@ -73,30 +75,33 @@ public Task SerializePropertiesAsync(IDictionary properties) if (properties.Count <= 0) return Task.CompletedTask; + return Task.Factory.StartNew(SerializeProperties, properties); + } + + [RequiresUnreferencedCode(TrimmerConstants.SerializerTrimmerWarning)] + void SerializeProperties(object properties) + { // Serialize property dictionary to local storage // Make sure to use Internal - return Task.Run(() => + using var stringWriter = new StringWriter(); + using var xmlWriter = XmlWriter.Create(stringWriter); + + try { - using var stringWriter = new StringWriter(); - using var xmlWriter = XmlWriter.Create(stringWriter); - - try - { - var dcs = new DataContractSerializer(typeof(Dictionary)); - dcs.WriteObject(xmlWriter, properties); - xmlWriter.Flush(); - - var str = stringWriter.ToString(); - - SaveSerialized(str); - } - catch (Exception e) - { - Debug.WriteLine("Could not serialize properties: " + e.Message); - Log.Warning("Microsoft.Maui.Controls.Compatibility PropertyStore", $"Exception while writing Application properties: {e}"); - return; - } - }); + var dcs = new DataContractSerializer(typeof(Dictionary)); + dcs.WriteObject(xmlWriter, properties); + xmlWriter.Flush(); + + var str = stringWriter.ToString(); + + SaveSerialized(str); + } + catch (Exception e) + { + Debug.WriteLine("Could not serialize properties: " + e.Message); + Log.Warning("Microsoft.Maui.Controls.Compatibility PropertyStore", $"Exception while writing Application properties: {e}"); + return; + } } } } \ No newline at end of file diff --git a/src/Compatibility/Core/src/iOS/NativeBindingService.cs b/src/Compatibility/Core/src/iOS/NativeBindingService.cs index f51ced5b5582..40166238df00 100644 --- a/src/Compatibility/Core/src/iOS/NativeBindingService.cs +++ b/src/Compatibility/Core/src/iOS/NativeBindingService.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using Microsoft.Maui.Controls.Internals; using Microsoft.Maui.Controls.Xaml.Internals; using UIKit; @@ -9,6 +10,7 @@ namespace Microsoft.Maui.Controls.Compatibility.Platform.iOS [Preserve(AllMembers = true)] class NativeBindingService : INativeBindingService { + [UnconditionalSuppressMessage ("Trimming", "IL2075", Justification = TrimmerConstants.NativeBindingService)] public bool TrySetBinding(object target, string propertyName, BindingBase binding) { var view = target as UIView; diff --git a/src/Controls/src/Core/DependencyService.cs b/src/Controls/src/Core/DependencyService.cs index a779dbcb94ec..9ba5b2353cb7 100644 --- a/src/Controls/src/Core/DependencyService.cs +++ b/src/Controls/src/Core/DependencyService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using Microsoft.Maui.Controls.Internals; @@ -16,14 +17,14 @@ public static class DependencyService static readonly List DependencyTypes = new List(); static readonly Dictionary DependencyImplementations = new Dictionary(); - public static T Resolve(DependencyFetchTarget fallbackFetchTarget = DependencyFetchTarget.GlobalInstance) where T : class + public static T Resolve<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(DependencyFetchTarget fallbackFetchTarget = DependencyFetchTarget.GlobalInstance) where T : class { var result = DependencyResolver.Resolve(typeof(T)) as T; return result ?? Get(fallbackFetchTarget); } - public static T Get(DependencyFetchTarget fetchTarget = DependencyFetchTarget.GlobalInstance) where T : class + public static T Get<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(DependencyFetchTarget fetchTarget = DependencyFetchTarget.GlobalInstance) where T : class { Initialize(); @@ -58,14 +59,14 @@ public static T Get(DependencyFetchTarget fetchTarget = DependencyFetchTarget return (T)Activator.CreateInstance(dependencyImplementation.ImplementorType); } - public static void Register() where T : class + public static void Register<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>() where T : class { Type type = typeof(T); if (!DependencyTypes.Contains(type)) DependencyTypes.Add(type); } - public static void Register() where T : class where TImpl : class, T + public static void Register() where T : class where TImpl : class, T { Type targetType = typeof(T); Type implementorType = typeof(TImpl); diff --git a/src/Controls/src/Core/IDeserializer.cs b/src/Controls/src/Core/IDeserializer.cs index 6b90db802402..d93c6656ee0b 100644 --- a/src/Controls/src/Core/IDeserializer.cs +++ b/src/Controls/src/Core/IDeserializer.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; namespace Microsoft.Maui.Controls.Internals @@ -7,7 +8,9 @@ namespace Microsoft.Maui.Controls.Internals [EditorBrowsable(EditorBrowsableState.Never)] public interface IDeserializer { + [RequiresUnreferencedCode(TrimmerConstants.SerializerTrimmerWarning)] Task> DeserializePropertiesAsync(); + [RequiresUnreferencedCode(TrimmerConstants.SerializerTrimmerWarning)] Task SerializePropertiesAsync(IDictionary properties); } } \ No newline at end of file diff --git a/src/Controls/src/Core/TrimmerConstants.cs b/src/Controls/src/Core/TrimmerConstants.cs new file mode 100644 index 000000000000..c094e541c5ad --- /dev/null +++ b/src/Controls/src/Core/TrimmerConstants.cs @@ -0,0 +1,9 @@ +namespace Microsoft.Maui.Controls; + +class TrimmerConstants +{ + // https://github.com/dotnet/runtime/blob/f130138b337b57342e94dabf499b818531effed5/src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/DataContract.cs#L31-L32 + internal const string SerializerTrimmerWarning = "Data Contract Serialization and Deserialization might require types that cannot be statically analyzed. Make sure all of the required types are preserved."; + + internal const string NativeBindingService = "This method properly handles missing properties, and there is not a way to preserve them from this method."; +} \ No newline at end of file diff --git a/src/Core/src/System.Diagnostics.CodeAnalysis/DynamicDependencyAttribute.cs b/src/Core/src/System.Diagnostics.CodeAnalysis/DynamicDependencyAttribute.cs new file mode 100644 index 000000000000..05c0ceb456b2 --- /dev/null +++ b/src/Core/src/System.Diagnostics.CodeAnalysis/DynamicDependencyAttribute.cs @@ -0,0 +1,131 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +#if NETSTANDARD2_0 || NETSTANDARD2_1 +namespace System.Diagnostics.CodeAnalysis +{ + /// + /// States a dependency that one member has on another. + /// + /// + /// This can be used to inform tooling of a dependency that is otherwise not evident purely from + /// metadata and IL, for example a member relied on via reflection. + /// + [AttributeUsage( + AttributeTargets.Constructor | AttributeTargets.Field | AttributeTargets.Method, + AllowMultiple = true, Inherited = false)] + internal sealed class DynamicDependencyAttribute : Attribute + { + /// + /// Initializes a new instance of the class + /// with the specified signature of a member on the same type as the consumer. + /// + /// The signature of the member depended on. + public DynamicDependencyAttribute(string memberSignature) + { + MemberSignature = memberSignature; + } + + /// + /// Initializes a new instance of the class + /// with the specified signature of a member on a . + /// + /// The signature of the member depended on. + /// The containing . + public DynamicDependencyAttribute(string memberSignature, Type type) + { + MemberSignature = memberSignature; + Type = type; + } + + /// + /// Initializes a new instance of the class + /// with the specified signature of a member on a type in an assembly. + /// + /// The signature of the member depended on. + /// The full name of the type containing the specified member. + /// The assembly name of the type containing the specified member. + public DynamicDependencyAttribute(string memberSignature, string typeName, string assemblyName) + { + MemberSignature = memberSignature; + TypeName = typeName; + AssemblyName = assemblyName; + } + + /// + /// Initializes a new instance of the class + /// with the specified types of members on a . + /// + /// The types of members depended on. + /// The containing the specified members. + public DynamicDependencyAttribute(DynamicallyAccessedMemberTypes memberTypes, Type type) + { + MemberTypes = memberTypes; + Type = type; + } + + /// + /// Initializes a new instance of the class + /// with the specified types of members on a type in an assembly. + /// + /// The types of members depended on. + /// The full name of the type containing the specified members. + /// The assembly name of the type containing the specified members. + public DynamicDependencyAttribute(DynamicallyAccessedMemberTypes memberTypes, string typeName, string assemblyName) + { + MemberTypes = memberTypes; + TypeName = typeName; + AssemblyName = assemblyName; + } + + /// + /// Gets the signature of the member depended on. + /// + /// + /// Either must be a valid string or + /// must not equal , but not both. + /// + public string? MemberSignature { get; } + + /// + /// Gets the which specifies the type + /// of members depended on. + /// + /// + /// Either must be a valid string or + /// must not equal , but not both. + /// + public DynamicallyAccessedMemberTypes MemberTypes { get; } + + /// + /// Gets the containing the specified member. + /// + /// + /// If neither nor are specified, + /// the type of the consumer is assumed. + /// + public Type? Type { get; } + + /// + /// Gets the full name of the type containing the specified member. + /// + /// + /// If neither nor are specified, + /// the type of the consumer is assumed. + /// + public string? TypeName { get; } + + /// + /// Gets the assembly name of the specified type. + /// + /// + /// is only valid when is specified. + /// + public string? AssemblyName { get; } + + /// + /// Gets or sets the condition in which the dependency is applicable, e.g. "DEBUG". + /// + public string? Condition { get; set; } + } +} +#endif diff --git a/src/Core/src/System.Diagnostics.CodeAnalysis/DynamicallyAccessedMemberTypes.cs b/src/Core/src/System.Diagnostics.CodeAnalysis/DynamicallyAccessedMemberTypes.cs new file mode 100644 index 000000000000..59b523ccd428 --- /dev/null +++ b/src/Core/src/System.Diagnostics.CodeAnalysis/DynamicallyAccessedMemberTypes.cs @@ -0,0 +1,91 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +#if NETSTANDARD2_0 || NETSTANDARD2_1 +namespace System.Diagnostics.CodeAnalysis +{ + /// + /// Specifies the types of members that are dynamically accessed. + /// + /// This enumeration has a attribute that allows a + /// bitwise combination of its member values. + /// + [Flags] + internal enum DynamicallyAccessedMemberTypes + { + /// + /// Specifies no members. + /// + None = 0, + + /// + /// Specifies the default, parameterless public constructor. + /// + PublicParameterlessConstructor = 0x0001, + + /// + /// Specifies all public constructors. + /// + PublicConstructors = 0x0002 | PublicParameterlessConstructor, + + /// + /// Specifies all non-public constructors. + /// + NonPublicConstructors = 0x0004, + + /// + /// Specifies all public methods. + /// + PublicMethods = 0x0008, + + /// + /// Specifies all non-public methods. + /// + NonPublicMethods = 0x0010, + + /// + /// Specifies all public fields. + /// + PublicFields = 0x0020, + + /// + /// Specifies all non-public fields. + /// + NonPublicFields = 0x0040, + + /// + /// Specifies all public nested types. + /// + PublicNestedTypes = 0x0080, + + /// + /// Specifies all non-public nested types. + /// + NonPublicNestedTypes = 0x0100, + + /// + /// Specifies all public properties. + /// + PublicProperties = 0x0200, + + /// + /// Specifies all non-public properties. + /// + NonPublicProperties = 0x0400, + + /// + /// Specifies all public events. + /// + PublicEvents = 0x0800, + + /// + /// Specifies all non-public events. + /// + NonPublicEvents = 0x1000, + + /// + /// Specifies all members. + /// + All = ~None + } +} +#endif diff --git a/src/Core/src/System.Diagnostics.CodeAnalysis/DynamicallyAccessedMembersAttribute.cs b/src/Core/src/System.Diagnostics.CodeAnalysis/DynamicallyAccessedMembersAttribute.cs new file mode 100644 index 000000000000..9243e06fe7a2 --- /dev/null +++ b/src/Core/src/System.Diagnostics.CodeAnalysis/DynamicallyAccessedMembersAttribute.cs @@ -0,0 +1,57 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +#if NETSTANDARD2_0 || NETSTANDARD2_1 +namespace System.Diagnostics.CodeAnalysis +{ + /// + /// Indicates that certain members on a specified are accessed dynamically, + /// for example through . + /// + /// + /// This allows tools to understand which members are being accessed during the execution + /// of a program. + /// + /// This attribute is valid on members whose type is or . + /// + /// When this attribute is applied to a location of type , the assumption is + /// that the string represents a fully qualified type name. + /// + /// When this attribute is applied to a class, interface, or struct, the members specified + /// can be accessed dynamically on instances returned from calling + /// on instances of that class, interface, or struct. + /// + /// If the attribute is applied to a method it's treated as a special case and it implies + /// the attribute should be applied to the "this" parameter of the method. As such the attribute + /// should only be used on instance methods of types assignable to System.Type (or string, but no methods + /// will use it there). + /// + [AttributeUsage( + AttributeTargets.Field | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter | + AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Method | + AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct, + Inherited = false)] +#if SYSTEM_PRIVATE_CORELIB + public +#else + internal +#endif + sealed class DynamicallyAccessedMembersAttribute : Attribute + { + /// + /// Initializes a new instance of the class + /// with the specified member types. + /// + /// The types of members dynamically accessed. + public DynamicallyAccessedMembersAttribute(DynamicallyAccessedMemberTypes memberTypes) + { + MemberTypes = memberTypes; + } + + /// + /// Gets the which specifies the type + /// of members dynamically accessed. + /// + public DynamicallyAccessedMemberTypes MemberTypes { get; } + } +} +#endif diff --git a/src/Core/src/System.Diagnostics.CodeAnalysis/RequiresUnreferencedCodeAttribute.cs b/src/Core/src/System.Diagnostics.CodeAnalysis/RequiresUnreferencedCodeAttribute.cs new file mode 100644 index 000000000000..d82d4c703420 --- /dev/null +++ b/src/Core/src/System.Diagnostics.CodeAnalysis/RequiresUnreferencedCodeAttribute.cs @@ -0,0 +1,48 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable +#if NETSTANDARD2_0 || NETSTANDARD2_1 +namespace System.Diagnostics.CodeAnalysis +{ + /// + /// Indicates that the specified method requires dynamic access to code that is not referenced + /// statically, for example through . + /// + /// + /// This allows tools to understand which methods are unsafe to call when removing unreferenced + /// code from an application. + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Class, Inherited = false)] +#if SYSTEM_PRIVATE_CORELIB + public +#else + internal +#endif + sealed class RequiresUnreferencedCodeAttribute : Attribute + { + /// + /// Initializes a new instance of the class + /// with the specified message. + /// + /// + /// A message that contains information about the usage of unreferenced code. + /// + public RequiresUnreferencedCodeAttribute(string message) + { + Message = message; + } + + /// + /// Gets a message that contains information about the usage of unreferenced code. + /// + public string Message { get; } + + /// + /// Gets or sets an optional URL that contains more information about the method, + /// why it requries unreferenced code, and what options a consumer has to deal with it. + /// + public string? Url { get; set; } + } +} +#endif diff --git a/src/Core/src/System.Diagnostics.CodeAnalysis/UnconditionalSuppressMessageAttribute.cs b/src/Core/src/System.Diagnostics.CodeAnalysis/UnconditionalSuppressMessageAttribute.cs new file mode 100644 index 000000000000..4ac333f96c0b --- /dev/null +++ b/src/Core/src/System.Diagnostics.CodeAnalysis/UnconditionalSuppressMessageAttribute.cs @@ -0,0 +1,88 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable +#if NETSTANDARD2_0 || NETSTANDARD2_1 +namespace System.Diagnostics.CodeAnalysis +{ + /// + /// Suppresses reporting of a specific rule violation, allowing multiple suppressions on a + /// single code artifact. + /// + /// + /// is different than + /// in that it doesn't have a + /// . So it is always preserved in the compiled assembly. + /// + [AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)] + internal sealed class UnconditionalSuppressMessageAttribute : Attribute + { + /// + /// Initializes a new instance of the + /// class, specifying the category of the tool and the identifier for an analysis rule. + /// + /// The category for the attribute. + /// The identifier of the analysis rule the attribute applies to. + public UnconditionalSuppressMessageAttribute(string category, string checkId) + { + Category = category; + CheckId = checkId; + } + + /// + /// Gets the category identifying the classification of the attribute. + /// + /// + /// The property describes the tool or tool analysis category + /// for which a message suppression attribute applies. + /// + public string Category { get; } + + /// + /// Gets the identifier of the analysis tool rule to be suppressed. + /// + /// + /// Concatenated together, the and + /// properties form a unique check identifier. + /// + public string CheckId { get; } + + /// + /// Gets or sets the scope of the code that is relevant for the attribute. + /// + /// + /// The Scope property is an optional argument that specifies the metadata scope for which + /// the attribute is relevant. + /// + public string? Scope { get; set; } + + /// + /// Gets or sets a fully qualified path that represents the target of the attribute. + /// + /// + /// The property is an optional argument identifying the analysis target + /// of the attribute. An example value is "System.IO.Stream.ctor():System.Void". + /// Because it is fully qualified, it can be long, particularly for targets such as parameters. + /// The analysis tool user interface should be capable of automatically formatting the parameter. + /// + public string? Target { get; set; } + + /// + /// Gets or sets an optional argument expanding on exclusion criteria. + /// + /// + /// The property is an optional argument that specifies additional + /// exclusion where the literal metadata target is not sufficiently precise. For example, + /// the cannot be applied within a method, + /// and it may be desirable to suppress a violation against a statement in the method that will + /// give a rule violation, but not against all statements in the method. + /// + public string? MessageId { get; set; } + + /// + /// Gets or sets the justification for suppressing the code analysis message. + /// + public string? Justification { get; set; } + } +} +#endif