diff --git a/docs/building-apps/build-properties.md b/docs/building-apps/build-properties.md index 6089dc2ed05c..3b56409369d9 100644 --- a/docs/building-apps/build-properties.md +++ b/docs/building-apps/build-properties.md @@ -543,6 +543,20 @@ Removing the dynamic registrar requires a static registrar (`Registrar=static` o `Registrar=managed-static`) and trimming, so setting this property has no effect (and the build warns) when those conditions aren't met. +## TrimExportAttributes + +Controls whether `Foundation.ExportAttribute`, `Foundation.ActionAttribute`, +and `Foundation.OutletAttribute` instances are removed during trimming. + +If this property is not specified, the build automatically removes these +attributes when assembly preparation and post-processing are enabled, the +trimmable static registrar is selected, dynamic registration is not required, +and no runtime fallback needs the attributes. + +Set this property to `false` to preserve the attributes. Set it to `true` to +require their removal; the build will fail if it detects that the attributes +are needed at runtime. + ## EmbedOnDemandResources Controls where on-demand resource asset packs are placed, so that the on-demand diff --git a/dotnet/targets/Xamarin.Shared.Sdk.targets b/dotnet/targets/Xamarin.Shared.Sdk.targets index 65987e7381b7..15627866305d 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.targets +++ b/dotnet/targets/Xamarin.Shared.Sdk.targets @@ -294,6 +294,7 @@ _PrepareAssemblies; _SetDynamicRegistrationSupportedFeature; _SetSmartEnumConversionsRootDescriptor; + _SetTrimExportAttributesFeature; _ComputeFrameworkFilesToPublish; _ComputeDynamicLibrariesToPublish; ComputeFilesToPublish; @@ -642,7 +643,7 @@ + + + + + + <_TrimExportAttributesLine Include="@(_AssemblyPreparerTrimExportAttributesProperty)" Condition="$([System.String]::Copy('%(Identity)').StartsWith('TrimExportAttributes='))" /> + + + <_TrimExportAttributesLineText>@(_TrimExportAttributesLine) + <_TrimExportAttributes>false + <_TrimExportAttributes Condition="'$(_TrimExportAttributesLineText)' != ''">$(_TrimExportAttributesLineText.Replace('TrimExportAttributes=', '')) + + + + + + + + @@ -726,8 +760,16 @@ /> + + + <_TrimExportAttributes>false + <_TrimExportAttributes Condition="'$(PrepareAssemblies)' == 'true' And '$(PostProcessAssemblies)' == 'true' And '$(Registrar)' == 'trimmable-static'">$(TrimExportAttributes) + <_UseDynamicDependenciesInsteadOfMarking Condition="'$(_UseDynamicDependenciesInsteadOfMarking)' == ''">true <_UseDynamicDependenciesForProtocolPreservation Condition="'$(_UseDynamicDependenciesForProtocolPreservation)' == ''">$(_UseDynamicDependenciesInsteadOfMarking) <_UseDynamicDependenciesForSmartEnumPreservation Condition="'$(_UseDynamicDependenciesForSmartEnumPreservation)' == ''">$(_UseDynamicDependenciesInsteadOfMarking) @@ -898,6 +940,7 @@ SkipMarkingNSObjectsInUserAssemblies=$(_SkipMarkingNSObjectsInUserAssemblies) TargetArchitectures=$(TargetArchitectures) TargetFramework=$(_ComputedTargetFrameworkMoniker) + TrimExportAttributes=$(_TrimExportAttributes) TrimMode=$(TrimMode) TypeMapAssemblyName=$(_TypeMapAssemblyName) TypeMapFilePath=$(_TypeMapFilePath) @@ -1027,7 +1070,7 @@ <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.MarkNSObjectsStep" Condition="'$(PrepareAssemblies)' != 'true' And '$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForMarkNSObjects)' == 'true'" /> <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.InlineDlfcnMethodsStep" Condition="'$(PrepareAssemblies)' != 'true' And '$(InlineDlfcnMethods)' != ''" /> - <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="MonoTouch.Tuner.RegistrarRemovalTrackingStep" Condition="'$(PrepareAssemblies)' != 'true'" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="MonoTouch.Tuner.DetectApiUsageStep" Condition="'$(PrepareAssemblies)' != 'true'" /> <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.PreMarkDispatcher" Condition="'$(PrepareAssemblies)' != 'true' And '$(_AreAnyAssembliesTrimmed)' == 'true'" /> <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.ManagedRegistrarStep" Condition="'$(PrepareAssemblies)' != 'true' And ('$(Registrar)' == 'managed-static' Or '$(Registrar)' == 'trimmable-static')" /> diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/PrepareAssemblies.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/PrepareAssemblies.cs index 5419736fa4b6..5d360855e883 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/PrepareAssemblies.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/PrepareAssemblies.cs @@ -31,7 +31,7 @@ public class PrepareAssemblies : XamarinTask { public string MakeReproPath { get; set; } = ""; // The value of the $(_DynamicRegistrationSupported) MSBuild property. During post-processing this is - // how the value RegistrarRemovalTrackingStep computed during the preparation pass is passed back to + // how the value DetectApiUsageStep computed during the preparation pass is passed back to // the assembly-preparer (the native main file is generated during post-processing, and it must agree // with the managed side about whether the dynamic registrar is available). public string DynamicRegistrationSupported { get; set; } = ""; @@ -44,8 +44,8 @@ public class PrepareAssemblies : XamarinTask { public bool PostProcessing { get; set; } - // The pre-trim (untrimmed) assemblies (the trimmer's input), used during post-processing to read - // the [ProtocolMember] attributes the trimmer removed from the post-trim assemblies. + // The original assemblies from before preparation and trimming, used during post-processing to read + // selected registrar attributes removed during trimming. public ITaskItem [] PreTrimAssemblies { get; set; } = []; // When set (to ILC's output object file), the defined symbols in this file are used to determine @@ -78,10 +78,13 @@ public override bool Execute () { // Capture Console usage and show an error if anything uses Console.[Error.]Write* using var consoleToLog = ConsoleToTaskWriter.EnsureNoConsoleUsage (Log); + var success = false; + var msbuildOutputFile = ""; try { var infos = InputAssemblies.Select (GetAssemblyInfo).ToArray (); using var preparer = new AssemblyPreparer (this, infos, OptionsFile?.ItemSpec ?? ""); + msbuildOutputFile = PostProcessing ? preparer.Configuration.MSBuildPostProcessOutputFile : preparer.Configuration.MSBuildOutputFile; preparer.MakeReproPath = MakeReproPath; preparer.PreTrimAssemblies.AddRange (PreTrimAssemblies.Select (v => v.ItemSpec)); @@ -140,8 +143,10 @@ public override bool Execute () outputAssemblies.AddRange (preparer.AddedAssemblies.Select (v => { var rv = new TaskItem (v.Path); + var relativePath = preparer.Configuration.AssemblyPublishDir + Path.GetFileName (v.Path); rv.SetMetadata ("PostprocessAssembly", "true"); - rv.SetMetadata ("RelativePath", preparer.Configuration.AssemblyPublishDir + Path.GetFileName (v.Path)); + rv.SetMetadata ("OriginalRelativePath", relativePath); + rv.SetMetadata ("RelativePath", relativePath); if (v.OriginatingAssembly is not null) { var originatingAssembly = preparer.Assemblies.SingleOrDefault (assembly => assembly.InputPath == v.OriginatingAssembly); originatingAssembly ??= preparer.Assemblies.SingleOrDefault (assembly => assembly.IsCILAssembly && Path.GetFileName (assembly.InputPath) == Path.GetFileName (v.OriginatingAssembly)); @@ -162,10 +167,14 @@ public override bool Execute () OutputAssemblies = outputAssemblies.ToArray (); if (!rv && !Log.HasLoggedErrors) Log.LogError (MSBStrings.E0192); - return rv && !Log.HasLoggedErrors; + success = rv && !Log.HasLoggedErrors; + return success; } catch (Exception e) { ((IToolLog) this).LogException (e); return false; + } finally { + if (!success && !string.IsNullOrEmpty (msbuildOutputFile)) + File.Delete (msbuildOutputFile); } } } diff --git a/msbuild/Xamarin.Shared/Xamarin.Shared.targets b/msbuild/Xamarin.Shared/Xamarin.Shared.targets index 1bfd17c95dd6..96584743f73e 100644 --- a/msbuild/Xamarin.Shared/Xamarin.Shared.targets +++ b/msbuild/Xamarin.Shared/Xamarin.Shared.targets @@ -3715,13 +3715,20 @@ Copyright (C) 2018 Microsoft. All rights reserved. $([MSBuild]::EnsureTrailingSlash('$(DeviceSpecificIntermediateOutputPath)prepared-assemblies')) + <_AssemblyPreparerConfigurationStamp>$(DeviceSpecificIntermediateOutputPath)assembly-preparer-configuration.txt + @@ -3739,6 +3746,7 @@ Copyright (C) 2018 Microsoft. All rights reserved. + @@ -3780,7 +3788,7 @@ Copyright (C) 2018 Microsoft. All rights reserved. OptionsFile="$(_CustomLinkerOptionsFile)" OutputDirectory="$(_PostprocessedAssembliesDirectory)" Postprocessing="true" - PreTrimAssemblies="@(ManagedAssemblyToLink)" + PreTrimAssemblies="@(_AssembliesToPrepare)" TargetFrameworkMoniker="$(_ComputedTargetFrameworkMoniker)" > diff --git a/src/ILLink.LinkAttributes.xml.in b/src/ILLink.LinkAttributes.xml.in index 764df73f8023..c74e23e59c1c 100644 --- a/src/ILLink.LinkAttributes.xml.in +++ b/src/ILLink.LinkAttributes.xml.in @@ -7,6 +7,15 @@ + + + + + + + + + diff --git a/tests/assembly-preparer/ComputeExportAttributeRemovalStepTests.cs b/tests/assembly-preparer/ComputeExportAttributeRemovalStepTests.cs new file mode 100644 index 000000000000..dc594e46e70a --- /dev/null +++ b/tests/assembly-preparer/ComputeExportAttributeRemovalStepTests.cs @@ -0,0 +1,130 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using MonoTouch.Tuner; + +using Xamarin.Linker; + +namespace AssemblyPreparerTests; + +public class ComputeExportAttributeRemovalStepTests : BaseClass { + [TestCase (null, true)] + [TestCase (false, true)] + [TestCase (true, false)] + public void NonTrimmableRegistrar (bool? trimExportAttributes, bool expectedSuccess) + { + using var preparer = CreatePreparer (ApplePlatform.iOS, false, preparer => { + preparer.Registrar = RegistrarMode.ManagedStatic; + preparer.TrimExportAttributes = trimExportAttributes; + }, "public class C {}", out _); + + var success = preparer.Prepare (out var exceptions); + + Assert.That (success, Is.EqualTo (expectedSuccess), "Success"); + if (expectedSuccess) { + Assert.That (exceptions, Is.Empty, "Exceptions"); + Assert.That (preparer.TrimExportAttributes, Is.False, "Removal"); + } else { + Assert.That (exceptions, Has.Count.EqualTo (1), "Exceptions"); + Assert.That (exceptions [0].Message, Is.EqualTo ("Export attributes can only be trimmed with the trimmable static registrar."), "Error message"); + } + } + + [TestCase (false, false, false)] + [TestCase (null, false, true)] + [TestCase (true, false, true)] + [TestCase (null, true, false)] + public void DynamicRegistration (bool? trimExportAttributes, bool dynamicRegistrationSupported, bool expectedRemoval) + { + using var preparer = CreatePreparer (ApplePlatform.iOS, false, preparer => { + preparer.Registrar = RegistrarMode.TrimmableStatic; + preparer.TrimExportAttributes = trimExportAttributes; + preparer.Optimizations.RemoveDynamicRegistrar = !dynamicRegistrationSupported; + preparer.Optimizations.OptimizeBlockLiteralSetupBlock = true; + preparer.Optimizations.StaticBlockToDelegateLookup = true; + }, "public class C {}", out _); + + var context = preparer.Configuration.DerivedLinkContext; + new LoadAssembliesStep ().Process (context); + new ComputeExportAttributeRemovalStep ().Process (context); + + Assert.That (preparer.Configuration.Application.TrimExportAttributes, Is.EqualTo (expectedRemoval), "Removal"); + } + + [TestCase (true, true, true)] + [TestCase (false, true, false)] + [TestCase (true, false, false)] + public void RequiredOptimizations (bool optimizeBlockLiteralSetupBlock, bool staticBlockToDelegateLookup, bool expectedRemoval) + { + using var preparer = CreatePreparer (ApplePlatform.iOS, false, preparer => { + preparer.Registrar = RegistrarMode.TrimmableStatic; + preparer.TrimExportAttributes = true; + preparer.Optimizations.RemoveDynamicRegistrar = true; + preparer.Optimizations.OptimizeBlockLiteralSetupBlock = optimizeBlockLiteralSetupBlock; + preparer.Optimizations.StaticBlockToDelegateLookup = staticBlockToDelegateLookup; + }, "public class C {}", out _); + + var context = preparer.Configuration.DerivedLinkContext; + new LoadAssembliesStep ().Process (context); + new ComputeExportAttributeRemovalStep ().Process (context); + + Assert.That (preparer.Configuration.Application.TrimExportAttributes, Is.EqualTo (expectedRemoval), "Removal"); + } + + [Test] + public void ExplicitBlocker () + { + using var preparer = CreatePreparer (ApplePlatform.iOS, false, preparer => { + preparer.Registrar = RegistrarMode.TrimmableStatic; + preparer.TrimExportAttributes = true; + preparer.Optimizations.RemoveDynamicRegistrar = true; + preparer.Optimizations.OptimizeBlockLiteralSetupBlock = true; + preparer.Optimizations.StaticBlockToDelegateLookup = true; + }, "public class C {}", out _); + + var context = preparer.Configuration.DerivedLinkContext; + new LoadAssembliesStep ().Process (context); + preparer.Configuration.Application.TrimExportAttributesBlockers.Add (ExportAttributeRemovalBlocker.RuntimeGetBlockWrapperCreatorRequired); + new ComputeExportAttributeRemovalStep ().Process (context); + + Assert.That (preparer.Configuration.Application.TrimExportAttributes, Is.False, "Removal"); + } + + [Test] + public void NSXpcInterfaceMethodInfoOverload () + { + var code = """ + using System.Reflection; + using Foundation; + using ObjCRuntime; + + public class C { + public void GetAllowedClasses (NSXpcInterface value, MethodInfo method) + { + value.GetAllowedClasses (method, 0, false); + } + + public void SetAllowedClasses (NSXpcInterface value, MethodInfo method, NSSet classes) + { + value.SetAllowedClasses (method, classes, 0, false); + } + } + """; + + using var preparer = CreatePreparer (ApplePlatform.iOS, false, preparer => { + preparer.Registrar = RegistrarMode.TrimmableStatic; + preparer.TrimExportAttributes = true; + preparer.Optimizations.RemoveDynamicRegistrar = true; + preparer.Optimizations.OptimizeBlockLiteralSetupBlock = true; + preparer.Optimizations.StaticBlockToDelegateLookup = true; + }, code, out _); + + var context = preparer.Configuration.DerivedLinkContext; + new LoadAssembliesStep ().Process (context); + new DetectApiUsageStep ().Process (context); + new ComputeExportAttributeRemovalStep ().Process (context); + + Assert.That (preparer.Configuration.Application.TrimExportAttributes, Is.False, "Removal"); + Assert.That (preparer.Configuration.Application.TrimExportAttributesBlockers.Single (), Is.EqualTo (ExportAttributeRemovalBlocker.NSXpcInterfaceMethodInfoOverloadUsed), "Blocker"); + } +} diff --git a/tests/common/DotNet.cs b/tests/common/DotNet.cs index 8ee3922753c0..e292e0ef01dc 100644 --- a/tests/common/DotNet.cs +++ b/tests/common/DotNet.cs @@ -94,9 +94,9 @@ public static ExecutionResult AssertRun (string project, Dictionary? properties = null) + public static ExecutionResult AssertBuildFailure (string project, Dictionary? properties = null, string? target = null) { - var rv = Execute ("build", project, properties, false); + var rv = Execute ("build", project, properties, false, target: target); Assert.That (rv.ExitCode, Is.Not.EqualTo (0), "Unexpected success"); return rv; } diff --git a/tests/dotnet/ExportAttributeRemovalApp/AppDelegate.cs b/tests/dotnet/ExportAttributeRemovalApp/AppDelegate.cs new file mode 100644 index 000000000000..4926a096c764 --- /dev/null +++ b/tests/dotnet/ExportAttributeRemovalApp/AppDelegate.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Reflection; + +using Foundation; +using ObjCRuntime; + +namespace ExportAttributeRemovalApp { + [Register] + class ExportMetadataApplicationType : NSObject { + [Export ("applicationExport")] + public void ApplicationExport () + { + } + + [Action ("applicationAction:")] + public void ApplicationAction (NSObject sender) + { + } + + [Outlet ("applicationOutlet")] + public NSObject? ApplicationOutlet { get; set; } + + public override string Description => base.Description; + } + + public class Program { +#if EXPORT_ATTRIBUTE_REMOVAL_NSXPC + static void UseNSXpcInterfaceMethodInfoOverload (NSXpcInterface value, MethodInfo method) + { + value.GetAllowedClasses (method, 0, false); + } +#endif + + static int Main (string [] args) + { + GC.KeepAlive (typeof (NSObject)); + GC.KeepAlive (typeof (INSUrlSessionDelegate)); + GC.KeepAlive (typeof (ExportMetadataApplicationType)); + Console.WriteLine (NSBundle.MainBundle.BundlePath); + Console.WriteLine (new ExportMetadataApplicationType ().Description); + + return args.Length; + } + } +} diff --git a/tests/dotnet/ExportAttributeRemovalApp/iOS/ExportAttributeRemovalApp.csproj b/tests/dotnet/ExportAttributeRemovalApp/iOS/ExportAttributeRemovalApp.csproj new file mode 100644 index 000000000000..86d408734aa8 --- /dev/null +++ b/tests/dotnet/ExportAttributeRemovalApp/iOS/ExportAttributeRemovalApp.csproj @@ -0,0 +1,7 @@ + + + + net$(BundledNETCoreAppTargetFrameworkVersion)-ios + + + diff --git a/tests/dotnet/ExportAttributeRemovalApp/shared.csproj b/tests/dotnet/ExportAttributeRemovalApp/shared.csproj new file mode 100644 index 000000000000..ee28bc877c78 --- /dev/null +++ b/tests/dotnet/ExportAttributeRemovalApp/shared.csproj @@ -0,0 +1,16 @@ + + + + Exe + + ExportAttributeRemovalApp + com.xamarin.exportattributeremovalapp + $(DefineConstants);$(AdditionalDefineConstants) + + + + + + + + diff --git a/tests/dotnet/UnitTests/DynamicRegistrationSupportedTest.cs b/tests/dotnet/UnitTests/DynamicRegistrationSupportedTest.cs index 4c5c93c1b626..bc3b2e533d07 100644 --- a/tests/dotnet/UnitTests/DynamicRegistrationSupportedTest.cs +++ b/tests/dotnet/UnitTests/DynamicRegistrationSupportedTest.cs @@ -16,7 +16,7 @@ public void UserSpecifiedValue (ApplePlatform platform, string runtimeIdentifier { // When the user sets $(DynamicRegistrationSupported), the value must be passed straight through to // the 'ObjCRuntime.Runtime.DynamicRegistrationSupported' trimmer feature switch (the assembly-preparer - // doesn't need to compute it, so RegistrarRemovalTrackingStep is skipped). + // doesn't need to compute it, so DetectApiUsageStep is skipped). var project = "MySimpleApp"; Configuration.IgnoreIfIgnoredPlatform (platform); Configuration.AssertRuntimeIdentifiersAvailable (platform, runtimeIdentifiers); @@ -58,7 +58,7 @@ public void UserSpecifiedValue (ApplePlatform platform, string runtimeIdentifier public void SkippedWhenPlatformAssemblyNotTrimmed (ApplePlatform platform, string runtimeIdentifiers) { // When the platform assembly isn't being trimmed (link mode None), the dynamic registrar can't be - // removed, so RegistrarRemovalTrackingStep is skipped and no DynamicRegistrationSupported feature + // removed, so DetectApiUsageStep is skipped and no DynamicRegistrationSupported feature // switch is emitted (the dynamic registrar is kept, which is the default). var project = "MySimpleApp"; Configuration.IgnoreIfIgnoredPlatform (platform); diff --git a/tests/dotnet/UnitTests/PrepareAssembliesTest.cs b/tests/dotnet/UnitTests/PrepareAssembliesTest.cs index 0a5bb4a3c04a..42bb161abe14 100644 --- a/tests/dotnet/UnitTests/PrepareAssembliesTest.cs +++ b/tests/dotnet/UnitTests/PrepareAssembliesTest.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using Mono.Cecil; + #nullable enable namespace Xamarin.Tests { @@ -34,5 +36,125 @@ public void IncrementalBuild (ApplePlatform platform, string runtimeIdentifiers, // The second (incremental) build, without any changes, must also succeed. DotNet.AssertBuild (project_path, properties); } + + [TestCase (true, true, "trimmable-static", null, null, true)] + [TestCase (false, true, "trimmable-static", null, null, false)] + [TestCase (true, false, "trimmable-static", null, null, false)] + [TestCase (true, true, "managed-static", null, null, false)] + [TestCase (true, true, "trimmable-static", "false", null, false)] + [TestCase (true, true, "trimmable-static", null, "true", false)] + public void ExportAttributeRemovalEligibility (bool prepareAssemblies, bool postProcessAssemblies, string registrar, string? trimExportAttributes, string? dynamicRegistrationSupported, bool expectedRemoval) + { + var platform = ApplePlatform.iOS; + var runtimeIdentifiers = "iossimulator-arm64"; + var project = "ExportAttributeRemovalApp"; + var configuration = "Release"; + Configuration.IgnoreIfIgnoredPlatform (platform); + Configuration.AssertRuntimeIdentifiersAvailable (platform, runtimeIdentifiers); + + var projectPath = GetProjectPath (project, runtimeIdentifiers: runtimeIdentifiers, platform: platform, out _, configuration: configuration); + Clean (projectPath); + var properties = GetDefaultProperties (runtimeIdentifiers); + properties ["Configuration"] = configuration; + properties ["EnableAssemblyILStripping"] = "true"; + properties ["MtouchLink"] = registrar == "managed-static" ? "SdkOnly" : "Full"; + properties ["PublishReadyToRun"] = "false"; + properties ["PostProcessAssemblies"] = postProcessAssemblies.ToString (); + properties ["PrepareAssemblies"] = prepareAssemblies.ToString (); + properties ["Registrar"] = registrar; + if (trimExportAttributes is not null) + properties ["TrimExportAttributes"] = trimExportAttributes; + if (dynamicRegistrationSupported is not null) + properties ["DynamicRegistrationSupported"] = dynamicRegistrationSupported; + + string platformAssemblyPath; + string appAssemblyPath; + string? target = null; + if (!prepareAssemblies) { + DotNet.AssertBuild (projectPath, properties, target: "Compile"); + platformAssemblyPath = Configuration.GetBaseLibraryImplementations (platform).First (); + appAssemblyPath = Path.Combine (GetObjDir (projectPath, platform, runtimeIdentifiers, configuration), project + ".dll"); + } else { + target = "Compile;_ComputePublishTrimmed;_ComputeLinkMode;_ComputeLinkerArguments;_PrepareAssemblies;_SetDynamicRegistrationSupportedFeature;_SetTrimExportAttributesFeature;_ComputeFrameworkFilesToPublish;_ComputeDynamicLibrariesToPublish;ComputeFilesToPublish;_ComputeStripAssemblyIL;_StripAssemblyIL"; + var assemblyDirectory = Path.Combine (GetObjDir (projectPath, platform, runtimeIdentifiers, configuration), "stripped"); + DotNet.AssertBuild (projectPath, properties, target: target); + platformAssemblyPath = Path.Combine (assemblyDirectory, Configuration.GetBaseLibraryName (platform)); + appAssemblyPath = Path.Combine (assemblyDirectory, project + ".dll"); + } + + AssertExportMetadata (platformAssemblyPath, appAssemblyPath, !expectedRemoval); + + if (expectedRemoval) { + properties ["DynamicRegistrationSupported"] = "true"; + DotNet.AssertBuild (projectPath, properties, target: target); + AssertExportMetadata (platformAssemblyPath, appAssemblyPath, true); + } + } + + static void AssertExportMetadata (string platformAssemblyPath, string appAssemblyPath, bool expected) + { + using var platformAssembly = AssemblyDefinition.ReadAssembly (platformAssemblyPath); + var bundlePath = platformAssembly.MainModule.GetType ("Foundation.NSBundle").Properties.Single (v => v.Name == "BundlePath"); + AssertExport (bundlePath.GetMethod, expected, "direct-binding NSObject wrapper"); + + var nsObjectDescription = platformAssembly.MainModule.GetType ("Foundation.NSObject").Properties.Single (v => v.Name == "Description"); + AssertExport (nsObjectDescription.GetMethod, expected, "wrapper ancestor with application subclasses"); + + using var appAssembly = AssemblyDefinition.ReadAssembly (appAssemblyPath); + var applicationType = appAssembly.MainModule.GetType ("ExportAttributeRemovalApp.ExportMetadataApplicationType"); + AssertAttribute (applicationType.Methods.Single (v => v.Name == "ApplicationExport"), "ExportAttribute", expected, "application export"); + AssertAttribute (applicationType.Methods.Single (v => v.Name == "ApplicationAction"), "ActionAttribute", expected, "application action"); + AssertAttribute (applicationType.Properties.Single (v => v.Name == "ApplicationOutlet"), "OutletAttribute", expected, "application outlet"); + } + + [TestCase (false)] + [TestCase (true)] + public void ExportAttributeRemovalWithNSXpcInterfaceUsage (bool explicitlyEnabled) + { + var platform = ApplePlatform.iOS; + var runtimeIdentifiers = "iossimulator-arm64"; + var project = "ExportAttributeRemovalApp"; + var configuration = "Release"; + Configuration.IgnoreIfIgnoredPlatform (platform); + Configuration.AssertRuntimeIdentifiersAvailable (platform, runtimeIdentifiers); + + var projectPath = GetProjectPath (project, runtimeIdentifiers: runtimeIdentifiers, platform: platform, out _, configuration: configuration); + Clean (projectPath); + var properties = GetDefaultProperties (runtimeIdentifiers); + properties ["AdditionalDefineConstants"] = "EXPORT_ATTRIBUTE_REMOVAL_NSXPC"; + properties ["Configuration"] = configuration; + properties ["MtouchLink"] = "Full"; + properties ["PostProcessAssemblies"] = "true"; + properties ["PrepareAssemblies"] = "true"; + properties ["Registrar"] = "trimmable-static"; + properties ["DynamicRegistrationSupported"] = "false"; + if (explicitlyEnabled) + properties ["TrimExportAttributes"] = "true"; + + var target = "Compile;_ComputePublishTrimmed;_ComputeLinkMode;_ComputeLinkerArguments;_PrepareAssemblies"; + var expectedMessage = "Export attributes cannot be removed because the application uses an NSXpcInterface overload that obtains a selector from MethodInfo."; + if (explicitlyEnabled) { + for (var i = 0; i < 2; i++) { + var result = DotNet.AssertBuildFailure (projectPath, properties, target: target); + var errors = BinLog.GetBuildLogErrors (result.BinLogPath).ToArray (); + AssertErrorMessages (errors, expectedMessage); + } + } else { + var result = DotNet.AssertBuild (projectPath, properties, target: target); + var warnings = BinLog.GetBuildLogWarnings (result.BinLogPath).FilterWarnings (platform).ToArray (); + AssertWarningMessages (warnings, expectedMessage); + } + } + + static void AssertExport (ICustomAttributeProvider provider, bool expected, string message) + { + AssertAttribute (provider, "ExportAttribute", expected, message); + } + + static void AssertAttribute (ICustomAttributeProvider provider, string name, bool expected, string message) + { + var actual = provider.CustomAttributes.Any (v => v.AttributeType.Namespace == "Foundation" && v.AttributeType.Name == name); + Assert.That (actual, Is.EqualTo (expected), message); + } } } diff --git a/tests/dotnet/UnitTests/ProjectTest.cs b/tests/dotnet/UnitTests/ProjectTest.cs index 7db2614ce7e3..798b4e6746d8 100644 --- a/tests/dotnet/UnitTests/ProjectTest.cs +++ b/tests/dotnet/UnitTests/ProjectTest.cs @@ -2956,6 +2956,8 @@ public void PublishAotMonoTouchTest_NoIL2009 (ApplePlatform platform, string run new ExpectedBuildMessage ($"MSBuild", $"It's not safe to remove the dynamic registrar, because monotouchtest references 'ObjCRuntime.Runtime.ConnectMethod (System.Type, System.Reflection.MethodInfo, Foundation.ExportAttribute)'."), new ExpectedBuildMessage ($"MSBuild", $"It's not safe to remove the dynamic registrar, because monotouchtest references 'ObjCRuntime.Runtime.ConnectMethod (System.Type, System.Reflection.MethodInfo, ObjCRuntime.Selector)'."), new ExpectedBuildMessage ($"MSBuild", $"It's not safe to remove the dynamic registrar, because monotouchtest references 'ObjCRuntime.Runtime.RegisterAssembly (System.Reflection.Assembly)'."), + new ExpectedBuildMessage ($"MSBuild", $"Export attributes cannot be removed because the managed registrar must use Runtime.GetBlockWrapperCreator."), + new ExpectedBuildMessage ($"MSBuild", $"Export attributes cannot be removed because the managed registrar must use RegistrarHelper.GetBlockForDelegate."), new ExpectedBuildMessage ($"tests/bindings-test/RegistrarBindingTest.cs", $"Unable to locate the block to delegate conversion method for the method System.Void Xamarin.BindingTests.RegistrarBindingTest/FakePropertyBlock::set_MyOptionalProperty(Bindings.Test.SimpleCallback)'s parameter #1."), new ExpectedBuildMessage ($"tests/bindings-test/RegistrarBindingTest.cs", $"Unable to locate the block to delegate conversion method for the method System.Void Xamarin.BindingTests.RegistrarBindingTest/FakePropertyBlock::set_MyOptionalStaticProperty(Bindings.Test.SimpleCallback)'s parameter #1."), new ExpectedBuildMessage ($"tests/bindings-test/RegistrarBindingTest.cs", $"Unable to locate the block to delegate conversion method for the method System.Void Xamarin.BindingTests.RegistrarBindingTest/FakePropertyBlock::set_MyRequiredProperty(Bindings.Test.SimpleCallback)'s parameter #1."), diff --git a/tests/dotnet/UnitTests/expected/MacCatalyst-CoreCLR-Interpreter-preservedapis.txt b/tests/dotnet/UnitTests/expected/MacCatalyst-CoreCLR-Interpreter-preservedapis.txt index 38bd0035a6b4..3f7565cf4043 100644 --- a/tests/dotnet/UnitTests/expected/MacCatalyst-CoreCLR-Interpreter-preservedapis.txt +++ b/tests/dotnet/UnitTests/expected/MacCatalyst-CoreCLR-Interpreter-preservedapis.txt @@ -183,9 +183,6 @@ Microsoft.MacCatalyst.dll:CoreGraphics.CGRect.Equals(System.Object) Microsoft.MacCatalyst.dll:CoreGraphics.CGRect.GetHashCode() Microsoft.MacCatalyst.dll:CoreGraphics.CGRect.NSStringFromCGRect(CoreGraphics.CGRect) Microsoft.MacCatalyst.dll:CoreGraphics.CGRect.ToString() -Microsoft.MacCatalyst.dll:Foundation.ExportAttribute -Microsoft.MacCatalyst.dll:Foundation.ExportAttribute..ctor(System.String, ObjCRuntime.ArgumentSemantic) -Microsoft.MacCatalyst.dll:Foundation.ExportAttribute..ctor(System.String) Microsoft.MacCatalyst.dll:Foundation.INSObjectFactory Microsoft.MacCatalyst.dll:Foundation.INSObjectFactory._Xamarin_ConstructNSObject(ObjCRuntime.NativeHandle) Microsoft.MacCatalyst.dll:Foundation.ModelAttribute @@ -403,15 +400,6 @@ Microsoft.MacCatalyst.dll:Foundation.RegisterAttribute.get_IsWrapper() Microsoft.MacCatalyst.dll:Foundation.TrackedMemory Microsoft.MacCatalyst.dll:Foundation.You_Should_Not_Call_base_In_This_Method Microsoft.MacCatalyst.dll:Foundation.You_Should_Not_Call_base_In_This_Method..ctor() -Microsoft.MacCatalyst.dll:ObjCRuntime.ArgumentSemantic -Microsoft.MacCatalyst.dll:ObjCRuntime.ArgumentSemantic Foundation.ExportAttribute::semantic -Microsoft.MacCatalyst.dll:ObjCRuntime.ArgumentSemantic ObjCRuntime.ArgumentSemantic::Assign -Microsoft.MacCatalyst.dll:ObjCRuntime.ArgumentSemantic ObjCRuntime.ArgumentSemantic::Copy -Microsoft.MacCatalyst.dll:ObjCRuntime.ArgumentSemantic ObjCRuntime.ArgumentSemantic::None -Microsoft.MacCatalyst.dll:ObjCRuntime.ArgumentSemantic ObjCRuntime.ArgumentSemantic::Retain -Microsoft.MacCatalyst.dll:ObjCRuntime.ArgumentSemantic ObjCRuntime.ArgumentSemantic::Strong -Microsoft.MacCatalyst.dll:ObjCRuntime.ArgumentSemantic ObjCRuntime.ArgumentSemantic::UnsafeUnretained -Microsoft.MacCatalyst.dll:ObjCRuntime.ArgumentSemantic ObjCRuntime.ArgumentSemantic::Weak Microsoft.MacCatalyst.dll:ObjCRuntime.BlockCollector Microsoft.MacCatalyst.dll:ObjCRuntime.BlockCollector..ctor(System.IntPtr) Microsoft.MacCatalyst.dll:ObjCRuntime.BlockCollector.Add(System.IntPtr) @@ -1099,7 +1087,6 @@ Microsoft.MacCatalyst.dll:System.Int32 Foundation.NSDictionary::System.Collectio Microsoft.MacCatalyst.dll:System.Int32 Foundation.NSDictionary/d__66::<>1__state Microsoft.MacCatalyst.dll:System.Int32 Foundation.NSDictionary/d__66::<>7__wrap2 Microsoft.MacCatalyst.dll:System.Int32 Foundation.NSObjectFlag::value__ -Microsoft.MacCatalyst.dll:System.Int32 ObjCRuntime.ArgumentSemantic::value__ Microsoft.MacCatalyst.dll:System.Int32 ObjCRuntime.BlockCollector::count Microsoft.MacCatalyst.dll:System.Int32 ObjCRuntime.Dlfcn/Mode::value__ Microsoft.MacCatalyst.dll:System.Int32 ObjCRuntime.MarshalManagedExceptionMode::value__ @@ -1282,7 +1269,6 @@ Microsoft.MacCatalyst.dll:System.Runtime.InteropServices.NFloat CoreGraphics.CGR Microsoft.MacCatalyst.dll:System.Runtime.InteropServices.NFloat CoreGraphics.CGRect::y Microsoft.MacCatalyst.dll:System.Runtime.InteropServices.ObjectiveC.ObjectiveCMarshal/UnhandledExceptionPropagationHandler ObjCRuntime.Runtime/<>O::<0>__UnhandledExceptionPropagationHandler Microsoft.MacCatalyst.dll:System.String CoreFoundation.CFString::str -Microsoft.MacCatalyst.dll:System.String Foundation.ExportAttribute::selector Microsoft.MacCatalyst.dll:System.String Foundation.NSException::Name() Microsoft.MacCatalyst.dll:System.String Foundation.NSException::Reason() Microsoft.MacCatalyst.dll:System.String Foundation.NSNumber::StringValue() diff --git a/tests/dotnet/UnitTests/expected/MacCatalyst-CoreCLR-R2R-preservedapis.txt b/tests/dotnet/UnitTests/expected/MacCatalyst-CoreCLR-R2R-preservedapis.txt index 38bd0035a6b4..3f7565cf4043 100644 --- a/tests/dotnet/UnitTests/expected/MacCatalyst-CoreCLR-R2R-preservedapis.txt +++ b/tests/dotnet/UnitTests/expected/MacCatalyst-CoreCLR-R2R-preservedapis.txt @@ -183,9 +183,6 @@ Microsoft.MacCatalyst.dll:CoreGraphics.CGRect.Equals(System.Object) Microsoft.MacCatalyst.dll:CoreGraphics.CGRect.GetHashCode() Microsoft.MacCatalyst.dll:CoreGraphics.CGRect.NSStringFromCGRect(CoreGraphics.CGRect) Microsoft.MacCatalyst.dll:CoreGraphics.CGRect.ToString() -Microsoft.MacCatalyst.dll:Foundation.ExportAttribute -Microsoft.MacCatalyst.dll:Foundation.ExportAttribute..ctor(System.String, ObjCRuntime.ArgumentSemantic) -Microsoft.MacCatalyst.dll:Foundation.ExportAttribute..ctor(System.String) Microsoft.MacCatalyst.dll:Foundation.INSObjectFactory Microsoft.MacCatalyst.dll:Foundation.INSObjectFactory._Xamarin_ConstructNSObject(ObjCRuntime.NativeHandle) Microsoft.MacCatalyst.dll:Foundation.ModelAttribute @@ -403,15 +400,6 @@ Microsoft.MacCatalyst.dll:Foundation.RegisterAttribute.get_IsWrapper() Microsoft.MacCatalyst.dll:Foundation.TrackedMemory Microsoft.MacCatalyst.dll:Foundation.You_Should_Not_Call_base_In_This_Method Microsoft.MacCatalyst.dll:Foundation.You_Should_Not_Call_base_In_This_Method..ctor() -Microsoft.MacCatalyst.dll:ObjCRuntime.ArgumentSemantic -Microsoft.MacCatalyst.dll:ObjCRuntime.ArgumentSemantic Foundation.ExportAttribute::semantic -Microsoft.MacCatalyst.dll:ObjCRuntime.ArgumentSemantic ObjCRuntime.ArgumentSemantic::Assign -Microsoft.MacCatalyst.dll:ObjCRuntime.ArgumentSemantic ObjCRuntime.ArgumentSemantic::Copy -Microsoft.MacCatalyst.dll:ObjCRuntime.ArgumentSemantic ObjCRuntime.ArgumentSemantic::None -Microsoft.MacCatalyst.dll:ObjCRuntime.ArgumentSemantic ObjCRuntime.ArgumentSemantic::Retain -Microsoft.MacCatalyst.dll:ObjCRuntime.ArgumentSemantic ObjCRuntime.ArgumentSemantic::Strong -Microsoft.MacCatalyst.dll:ObjCRuntime.ArgumentSemantic ObjCRuntime.ArgumentSemantic::UnsafeUnretained -Microsoft.MacCatalyst.dll:ObjCRuntime.ArgumentSemantic ObjCRuntime.ArgumentSemantic::Weak Microsoft.MacCatalyst.dll:ObjCRuntime.BlockCollector Microsoft.MacCatalyst.dll:ObjCRuntime.BlockCollector..ctor(System.IntPtr) Microsoft.MacCatalyst.dll:ObjCRuntime.BlockCollector.Add(System.IntPtr) @@ -1099,7 +1087,6 @@ Microsoft.MacCatalyst.dll:System.Int32 Foundation.NSDictionary::System.Collectio Microsoft.MacCatalyst.dll:System.Int32 Foundation.NSDictionary/d__66::<>1__state Microsoft.MacCatalyst.dll:System.Int32 Foundation.NSDictionary/d__66::<>7__wrap2 Microsoft.MacCatalyst.dll:System.Int32 Foundation.NSObjectFlag::value__ -Microsoft.MacCatalyst.dll:System.Int32 ObjCRuntime.ArgumentSemantic::value__ Microsoft.MacCatalyst.dll:System.Int32 ObjCRuntime.BlockCollector::count Microsoft.MacCatalyst.dll:System.Int32 ObjCRuntime.Dlfcn/Mode::value__ Microsoft.MacCatalyst.dll:System.Int32 ObjCRuntime.MarshalManagedExceptionMode::value__ @@ -1282,7 +1269,6 @@ Microsoft.MacCatalyst.dll:System.Runtime.InteropServices.NFloat CoreGraphics.CGR Microsoft.MacCatalyst.dll:System.Runtime.InteropServices.NFloat CoreGraphics.CGRect::y Microsoft.MacCatalyst.dll:System.Runtime.InteropServices.ObjectiveC.ObjectiveCMarshal/UnhandledExceptionPropagationHandler ObjCRuntime.Runtime/<>O::<0>__UnhandledExceptionPropagationHandler Microsoft.MacCatalyst.dll:System.String CoreFoundation.CFString::str -Microsoft.MacCatalyst.dll:System.String Foundation.ExportAttribute::selector Microsoft.MacCatalyst.dll:System.String Foundation.NSException::Name() Microsoft.MacCatalyst.dll:System.String Foundation.NSException::Reason() Microsoft.MacCatalyst.dll:System.String Foundation.NSNumber::StringValue() diff --git a/tests/dotnet/UnitTests/expected/MacOSX-CoreCLR-R2R-size.txt b/tests/dotnet/UnitTests/expected/MacOSX-CoreCLR-R2R-size.txt index 3e88d0271157..e25f6a8d50d0 100644 --- a/tests/dotnet/UnitTests/expected/MacOSX-CoreCLR-R2R-size.txt +++ b/tests/dotnet/UnitTests/expected/MacOSX-CoreCLR-R2R-size.txt @@ -1,9 +1,9 @@ -AppBundleSize: 329,710,665 bytes (321,983.1 KB = 314.4 MB) +AppBundleSize: 329,715,517 bytes (321,987.8 KB = 314.4 MB) # The following list of files and their sizes is just informational / for review, and isn't used in the test: Contents/Info.plist: - 745 bytes (0.7 KB = 0.0 MB) + 749 bytes (0.7 KB = 0.0 MB) Contents/MacOS/SizeTestApp: - 7,351,240 bytes (7,178.9 KB = 7.0 MB) + 7,351,480 bytes (7,179.2 KB = 7.0 MB) Contents/MonoBundle/_Microsoft.macOS.TypeMaps.dll: 2,560 bytes (2.5 KB = 0.0 MB) Contents/MonoBundle/.xamarin/osx-arm64/_Microsoft.macOS.TypeMap.dll: @@ -31,7 +31,7 @@ Contents/MonoBundle/.xamarin/osx-arm64/Microsoft.Extensions.Options.dll: Contents/MonoBundle/.xamarin/osx-arm64/Microsoft.Extensions.Primitives.dll: 90,408 bytes (88.3 KB = 0.1 MB) Contents/MonoBundle/.xamarin/osx-arm64/Microsoft.macOS.dll: - 73,306,112 bytes (71,588.0 KB = 69.9 MB) + 73,308,672 bytes (71,590.5 KB = 69.9 MB) Contents/MonoBundle/.xamarin/osx-arm64/Microsoft.VisualBasic.Core.dll: 1,337,128 bytes (1,305.8 KB = 1.3 MB) Contents/MonoBundle/.xamarin/osx-arm64/Microsoft.VisualBasic.dll: @@ -401,7 +401,7 @@ Contents/MonoBundle/.xamarin/osx-x64/Microsoft.Extensions.Options.dll: Contents/MonoBundle/.xamarin/osx-x64/Microsoft.Extensions.Primitives.dll: 83,240 bytes (81.3 KB = 0.1 MB) Contents/MonoBundle/.xamarin/osx-x64/Microsoft.macOS.dll: - 62,666,752 bytes (61,198.0 KB = 59.8 MB) + 62,668,800 bytes (61,200.0 KB = 59.8 MB) Contents/MonoBundle/.xamarin/osx-x64/Microsoft.VisualBasic.Core.dll: 1,191,720 bytes (1,163.8 KB = 1.1 MB) Contents/MonoBundle/.xamarin/osx-x64/Microsoft.VisualBasic.dll: diff --git a/tests/dotnet/UnitTests/expected/MacOSX-NativeAOT-TrimmableStatic-size.txt b/tests/dotnet/UnitTests/expected/MacOSX-NativeAOT-TrimmableStatic-size.txt index dba976dc39fe..55dfcf5120cb 100644 --- a/tests/dotnet/UnitTests/expected/MacOSX-NativeAOT-TrimmableStatic-size.txt +++ b/tests/dotnet/UnitTests/expected/MacOSX-NativeAOT-TrimmableStatic-size.txt @@ -1,8 +1,8 @@ -AppBundleSize: 4,599,936 bytes (4,492.1 KB = 4.4 MB) +AppBundleSize: 4,599,917 bytes (4,492.1 KB = 4.4 MB) # The following list of files and their sizes is just informational / for review, and isn't used in the test: Contents/Info.plist: - 752 bytes (0.7 KB = 0.0 MB) + 749 bytes (0.7 KB = 0.0 MB) Contents/MacOS/SizeTestApp: - 4,599,176 bytes (4,491.4 KB = 4.4 MB) + 4,599,160 bytes (4,491.4 KB = 4.4 MB) Contents/PkgInfo: 8 bytes (0.0 KB = 0.0 MB) diff --git a/tests/dotnet/UnitTests/expected/MacOSX-NativeAOT-size.txt b/tests/dotnet/UnitTests/expected/MacOSX-NativeAOT-size.txt index dba976dc39fe..55dfcf5120cb 100644 --- a/tests/dotnet/UnitTests/expected/MacOSX-NativeAOT-size.txt +++ b/tests/dotnet/UnitTests/expected/MacOSX-NativeAOT-size.txt @@ -1,8 +1,8 @@ -AppBundleSize: 4,599,936 bytes (4,492.1 KB = 4.4 MB) +AppBundleSize: 4,599,917 bytes (4,492.1 KB = 4.4 MB) # The following list of files and their sizes is just informational / for review, and isn't used in the test: Contents/Info.plist: - 752 bytes (0.7 KB = 0.0 MB) + 749 bytes (0.7 KB = 0.0 MB) Contents/MacOS/SizeTestApp: - 4,599,176 bytes (4,491.4 KB = 4.4 MB) + 4,599,160 bytes (4,491.4 KB = 4.4 MB) Contents/PkgInfo: 8 bytes (0.0 KB = 0.0 MB) diff --git a/tests/dotnet/UnitTests/expected/TVOS-CoreCLR-Interpreter-preservedapis.txt b/tests/dotnet/UnitTests/expected/TVOS-CoreCLR-Interpreter-preservedapis.txt index 84eb345629fb..06c01435b0cf 100644 --- a/tests/dotnet/UnitTests/expected/TVOS-CoreCLR-Interpreter-preservedapis.txt +++ b/tests/dotnet/UnitTests/expected/TVOS-CoreCLR-Interpreter-preservedapis.txt @@ -167,9 +167,6 @@ Microsoft.tvOS.dll:CoreGraphics.CGRect.Equals(System.Object) Microsoft.tvOS.dll:CoreGraphics.CGRect.GetHashCode() Microsoft.tvOS.dll:CoreGraphics.CGRect.NSStringFromCGRect(CoreGraphics.CGRect) Microsoft.tvOS.dll:CoreGraphics.CGRect.ToString() -Microsoft.tvOS.dll:Foundation.ExportAttribute -Microsoft.tvOS.dll:Foundation.ExportAttribute..ctor(System.String, ObjCRuntime.ArgumentSemantic) -Microsoft.tvOS.dll:Foundation.ExportAttribute..ctor(System.String) Microsoft.tvOS.dll:Foundation.INSObjectFactory Microsoft.tvOS.dll:Foundation.INSObjectFactory._Xamarin_ConstructNSObject(ObjCRuntime.NativeHandle) Microsoft.tvOS.dll:Foundation.ModelAttribute @@ -391,15 +388,6 @@ Microsoft.tvOS.dll:ObjCRuntime.Arch Microsoft.tvOS.dll:ObjCRuntime.Arch ObjCRuntime.Arch::DEVICE Microsoft.tvOS.dll:ObjCRuntime.Arch ObjCRuntime.Arch::SIMULATOR Microsoft.tvOS.dll:ObjCRuntime.Arch ObjCRuntime.Runtime::Arch -Microsoft.tvOS.dll:ObjCRuntime.ArgumentSemantic -Microsoft.tvOS.dll:ObjCRuntime.ArgumentSemantic Foundation.ExportAttribute::semantic -Microsoft.tvOS.dll:ObjCRuntime.ArgumentSemantic ObjCRuntime.ArgumentSemantic::Assign -Microsoft.tvOS.dll:ObjCRuntime.ArgumentSemantic ObjCRuntime.ArgumentSemantic::Copy -Microsoft.tvOS.dll:ObjCRuntime.ArgumentSemantic ObjCRuntime.ArgumentSemantic::None -Microsoft.tvOS.dll:ObjCRuntime.ArgumentSemantic ObjCRuntime.ArgumentSemantic::Retain -Microsoft.tvOS.dll:ObjCRuntime.ArgumentSemantic ObjCRuntime.ArgumentSemantic::Strong -Microsoft.tvOS.dll:ObjCRuntime.ArgumentSemantic ObjCRuntime.ArgumentSemantic::UnsafeUnretained -Microsoft.tvOS.dll:ObjCRuntime.ArgumentSemantic ObjCRuntime.ArgumentSemantic::Weak Microsoft.tvOS.dll:ObjCRuntime.BlockCollector Microsoft.tvOS.dll:ObjCRuntime.BlockCollector..ctor(System.IntPtr) Microsoft.tvOS.dll:ObjCRuntime.BlockCollector.Add(System.IntPtr) @@ -1084,7 +1072,6 @@ Microsoft.tvOS.dll:System.Int32 Foundation.NSDictionary/d__66::<> Microsoft.tvOS.dll:System.Int32 Foundation.NSDictionary/d__66::<>7__wrap2 Microsoft.tvOS.dll:System.Int32 Foundation.NSObjectFlag::value__ Microsoft.tvOS.dll:System.Int32 ObjCRuntime.Arch::value__ -Microsoft.tvOS.dll:System.Int32 ObjCRuntime.ArgumentSemantic::value__ Microsoft.tvOS.dll:System.Int32 ObjCRuntime.BlockCollector::count Microsoft.tvOS.dll:System.Int32 ObjCRuntime.Dlfcn/Mode::value__ Microsoft.tvOS.dll:System.Int32 ObjCRuntime.MarshalManagedExceptionMode::value__ @@ -1265,7 +1252,6 @@ Microsoft.tvOS.dll:System.Runtime.InteropServices.NFloat CoreGraphics.CGRect::x Microsoft.tvOS.dll:System.Runtime.InteropServices.NFloat CoreGraphics.CGRect::y Microsoft.tvOS.dll:System.Runtime.InteropServices.ObjectiveC.ObjectiveCMarshal/UnhandledExceptionPropagationHandler ObjCRuntime.Runtime/<>O::<0>__UnhandledExceptionPropagationHandler Microsoft.tvOS.dll:System.String CoreFoundation.CFString::str -Microsoft.tvOS.dll:System.String Foundation.ExportAttribute::selector Microsoft.tvOS.dll:System.String Foundation.NSException::Name() Microsoft.tvOS.dll:System.String Foundation.NSException::Reason() Microsoft.tvOS.dll:System.String Foundation.NSNumber::StringValue() diff --git a/tests/dotnet/UnitTests/expected/TVOS-CoreCLR-R2R-preservedapis.txt b/tests/dotnet/UnitTests/expected/TVOS-CoreCLR-R2R-preservedapis.txt index 84eb345629fb..06c01435b0cf 100644 --- a/tests/dotnet/UnitTests/expected/TVOS-CoreCLR-R2R-preservedapis.txt +++ b/tests/dotnet/UnitTests/expected/TVOS-CoreCLR-R2R-preservedapis.txt @@ -167,9 +167,6 @@ Microsoft.tvOS.dll:CoreGraphics.CGRect.Equals(System.Object) Microsoft.tvOS.dll:CoreGraphics.CGRect.GetHashCode() Microsoft.tvOS.dll:CoreGraphics.CGRect.NSStringFromCGRect(CoreGraphics.CGRect) Microsoft.tvOS.dll:CoreGraphics.CGRect.ToString() -Microsoft.tvOS.dll:Foundation.ExportAttribute -Microsoft.tvOS.dll:Foundation.ExportAttribute..ctor(System.String, ObjCRuntime.ArgumentSemantic) -Microsoft.tvOS.dll:Foundation.ExportAttribute..ctor(System.String) Microsoft.tvOS.dll:Foundation.INSObjectFactory Microsoft.tvOS.dll:Foundation.INSObjectFactory._Xamarin_ConstructNSObject(ObjCRuntime.NativeHandle) Microsoft.tvOS.dll:Foundation.ModelAttribute @@ -391,15 +388,6 @@ Microsoft.tvOS.dll:ObjCRuntime.Arch Microsoft.tvOS.dll:ObjCRuntime.Arch ObjCRuntime.Arch::DEVICE Microsoft.tvOS.dll:ObjCRuntime.Arch ObjCRuntime.Arch::SIMULATOR Microsoft.tvOS.dll:ObjCRuntime.Arch ObjCRuntime.Runtime::Arch -Microsoft.tvOS.dll:ObjCRuntime.ArgumentSemantic -Microsoft.tvOS.dll:ObjCRuntime.ArgumentSemantic Foundation.ExportAttribute::semantic -Microsoft.tvOS.dll:ObjCRuntime.ArgumentSemantic ObjCRuntime.ArgumentSemantic::Assign -Microsoft.tvOS.dll:ObjCRuntime.ArgumentSemantic ObjCRuntime.ArgumentSemantic::Copy -Microsoft.tvOS.dll:ObjCRuntime.ArgumentSemantic ObjCRuntime.ArgumentSemantic::None -Microsoft.tvOS.dll:ObjCRuntime.ArgumentSemantic ObjCRuntime.ArgumentSemantic::Retain -Microsoft.tvOS.dll:ObjCRuntime.ArgumentSemantic ObjCRuntime.ArgumentSemantic::Strong -Microsoft.tvOS.dll:ObjCRuntime.ArgumentSemantic ObjCRuntime.ArgumentSemantic::UnsafeUnretained -Microsoft.tvOS.dll:ObjCRuntime.ArgumentSemantic ObjCRuntime.ArgumentSemantic::Weak Microsoft.tvOS.dll:ObjCRuntime.BlockCollector Microsoft.tvOS.dll:ObjCRuntime.BlockCollector..ctor(System.IntPtr) Microsoft.tvOS.dll:ObjCRuntime.BlockCollector.Add(System.IntPtr) @@ -1084,7 +1072,6 @@ Microsoft.tvOS.dll:System.Int32 Foundation.NSDictionary/d__66::<> Microsoft.tvOS.dll:System.Int32 Foundation.NSDictionary/d__66::<>7__wrap2 Microsoft.tvOS.dll:System.Int32 Foundation.NSObjectFlag::value__ Microsoft.tvOS.dll:System.Int32 ObjCRuntime.Arch::value__ -Microsoft.tvOS.dll:System.Int32 ObjCRuntime.ArgumentSemantic::value__ Microsoft.tvOS.dll:System.Int32 ObjCRuntime.BlockCollector::count Microsoft.tvOS.dll:System.Int32 ObjCRuntime.Dlfcn/Mode::value__ Microsoft.tvOS.dll:System.Int32 ObjCRuntime.MarshalManagedExceptionMode::value__ @@ -1265,7 +1252,6 @@ Microsoft.tvOS.dll:System.Runtime.InteropServices.NFloat CoreGraphics.CGRect::x Microsoft.tvOS.dll:System.Runtime.InteropServices.NFloat CoreGraphics.CGRect::y Microsoft.tvOS.dll:System.Runtime.InteropServices.ObjectiveC.ObjectiveCMarshal/UnhandledExceptionPropagationHandler ObjCRuntime.Runtime/<>O::<0>__UnhandledExceptionPropagationHandler Microsoft.tvOS.dll:System.String CoreFoundation.CFString::str -Microsoft.tvOS.dll:System.String Foundation.ExportAttribute::selector Microsoft.tvOS.dll:System.String Foundation.NSException::Name() Microsoft.tvOS.dll:System.String Foundation.NSException::Reason() Microsoft.tvOS.dll:System.String Foundation.NSNumber::StringValue() diff --git a/tests/dotnet/UnitTests/expected/iOS-CoreCLR-Interpreter-preservedapis.txt b/tests/dotnet/UnitTests/expected/iOS-CoreCLR-Interpreter-preservedapis.txt index c9d5794c3068..51bafb9b050c 100644 --- a/tests/dotnet/UnitTests/expected/iOS-CoreCLR-Interpreter-preservedapis.txt +++ b/tests/dotnet/UnitTests/expected/iOS-CoreCLR-Interpreter-preservedapis.txt @@ -167,9 +167,6 @@ Microsoft.iOS.dll:CoreGraphics.CGRect.Equals(System.Object) Microsoft.iOS.dll:CoreGraphics.CGRect.GetHashCode() Microsoft.iOS.dll:CoreGraphics.CGRect.NSStringFromCGRect(CoreGraphics.CGRect) Microsoft.iOS.dll:CoreGraphics.CGRect.ToString() -Microsoft.iOS.dll:Foundation.ExportAttribute -Microsoft.iOS.dll:Foundation.ExportAttribute..ctor(System.String, ObjCRuntime.ArgumentSemantic) -Microsoft.iOS.dll:Foundation.ExportAttribute..ctor(System.String) Microsoft.iOS.dll:Foundation.INSObjectFactory Microsoft.iOS.dll:Foundation.INSObjectFactory._Xamarin_ConstructNSObject(ObjCRuntime.NativeHandle) Microsoft.iOS.dll:Foundation.ModelAttribute @@ -391,15 +388,6 @@ Microsoft.iOS.dll:ObjCRuntime.Arch Microsoft.iOS.dll:ObjCRuntime.Arch ObjCRuntime.Arch::DEVICE Microsoft.iOS.dll:ObjCRuntime.Arch ObjCRuntime.Arch::SIMULATOR Microsoft.iOS.dll:ObjCRuntime.Arch ObjCRuntime.Runtime::Arch -Microsoft.iOS.dll:ObjCRuntime.ArgumentSemantic -Microsoft.iOS.dll:ObjCRuntime.ArgumentSemantic Foundation.ExportAttribute::semantic -Microsoft.iOS.dll:ObjCRuntime.ArgumentSemantic ObjCRuntime.ArgumentSemantic::Assign -Microsoft.iOS.dll:ObjCRuntime.ArgumentSemantic ObjCRuntime.ArgumentSemantic::Copy -Microsoft.iOS.dll:ObjCRuntime.ArgumentSemantic ObjCRuntime.ArgumentSemantic::None -Microsoft.iOS.dll:ObjCRuntime.ArgumentSemantic ObjCRuntime.ArgumentSemantic::Retain -Microsoft.iOS.dll:ObjCRuntime.ArgumentSemantic ObjCRuntime.ArgumentSemantic::Strong -Microsoft.iOS.dll:ObjCRuntime.ArgumentSemantic ObjCRuntime.ArgumentSemantic::UnsafeUnretained -Microsoft.iOS.dll:ObjCRuntime.ArgumentSemantic ObjCRuntime.ArgumentSemantic::Weak Microsoft.iOS.dll:ObjCRuntime.BlockCollector Microsoft.iOS.dll:ObjCRuntime.BlockCollector..ctor(System.IntPtr) Microsoft.iOS.dll:ObjCRuntime.BlockCollector.Add(System.IntPtr) @@ -1084,7 +1072,6 @@ Microsoft.iOS.dll:System.Int32 Foundation.NSDictionary/d__66::<>1 Microsoft.iOS.dll:System.Int32 Foundation.NSDictionary/d__66::<>7__wrap2 Microsoft.iOS.dll:System.Int32 Foundation.NSObjectFlag::value__ Microsoft.iOS.dll:System.Int32 ObjCRuntime.Arch::value__ -Microsoft.iOS.dll:System.Int32 ObjCRuntime.ArgumentSemantic::value__ Microsoft.iOS.dll:System.Int32 ObjCRuntime.BlockCollector::count Microsoft.iOS.dll:System.Int32 ObjCRuntime.Dlfcn/Mode::value__ Microsoft.iOS.dll:System.Int32 ObjCRuntime.MarshalManagedExceptionMode::value__ @@ -1265,7 +1252,6 @@ Microsoft.iOS.dll:System.Runtime.InteropServices.NFloat CoreGraphics.CGRect::x Microsoft.iOS.dll:System.Runtime.InteropServices.NFloat CoreGraphics.CGRect::y Microsoft.iOS.dll:System.Runtime.InteropServices.ObjectiveC.ObjectiveCMarshal/UnhandledExceptionPropagationHandler ObjCRuntime.Runtime/<>O::<0>__UnhandledExceptionPropagationHandler Microsoft.iOS.dll:System.String CoreFoundation.CFString::str -Microsoft.iOS.dll:System.String Foundation.ExportAttribute::selector Microsoft.iOS.dll:System.String Foundation.NSException::Name() Microsoft.iOS.dll:System.String Foundation.NSException::Reason() Microsoft.iOS.dll:System.String Foundation.NSNumber::StringValue() diff --git a/tests/dotnet/UnitTests/expected/iOS-CoreCLR-R2R-preservedapis.txt b/tests/dotnet/UnitTests/expected/iOS-CoreCLR-R2R-preservedapis.txt index c9d5794c3068..51bafb9b050c 100644 --- a/tests/dotnet/UnitTests/expected/iOS-CoreCLR-R2R-preservedapis.txt +++ b/tests/dotnet/UnitTests/expected/iOS-CoreCLR-R2R-preservedapis.txt @@ -167,9 +167,6 @@ Microsoft.iOS.dll:CoreGraphics.CGRect.Equals(System.Object) Microsoft.iOS.dll:CoreGraphics.CGRect.GetHashCode() Microsoft.iOS.dll:CoreGraphics.CGRect.NSStringFromCGRect(CoreGraphics.CGRect) Microsoft.iOS.dll:CoreGraphics.CGRect.ToString() -Microsoft.iOS.dll:Foundation.ExportAttribute -Microsoft.iOS.dll:Foundation.ExportAttribute..ctor(System.String, ObjCRuntime.ArgumentSemantic) -Microsoft.iOS.dll:Foundation.ExportAttribute..ctor(System.String) Microsoft.iOS.dll:Foundation.INSObjectFactory Microsoft.iOS.dll:Foundation.INSObjectFactory._Xamarin_ConstructNSObject(ObjCRuntime.NativeHandle) Microsoft.iOS.dll:Foundation.ModelAttribute @@ -391,15 +388,6 @@ Microsoft.iOS.dll:ObjCRuntime.Arch Microsoft.iOS.dll:ObjCRuntime.Arch ObjCRuntime.Arch::DEVICE Microsoft.iOS.dll:ObjCRuntime.Arch ObjCRuntime.Arch::SIMULATOR Microsoft.iOS.dll:ObjCRuntime.Arch ObjCRuntime.Runtime::Arch -Microsoft.iOS.dll:ObjCRuntime.ArgumentSemantic -Microsoft.iOS.dll:ObjCRuntime.ArgumentSemantic Foundation.ExportAttribute::semantic -Microsoft.iOS.dll:ObjCRuntime.ArgumentSemantic ObjCRuntime.ArgumentSemantic::Assign -Microsoft.iOS.dll:ObjCRuntime.ArgumentSemantic ObjCRuntime.ArgumentSemantic::Copy -Microsoft.iOS.dll:ObjCRuntime.ArgumentSemantic ObjCRuntime.ArgumentSemantic::None -Microsoft.iOS.dll:ObjCRuntime.ArgumentSemantic ObjCRuntime.ArgumentSemantic::Retain -Microsoft.iOS.dll:ObjCRuntime.ArgumentSemantic ObjCRuntime.ArgumentSemantic::Strong -Microsoft.iOS.dll:ObjCRuntime.ArgumentSemantic ObjCRuntime.ArgumentSemantic::UnsafeUnretained -Microsoft.iOS.dll:ObjCRuntime.ArgumentSemantic ObjCRuntime.ArgumentSemantic::Weak Microsoft.iOS.dll:ObjCRuntime.BlockCollector Microsoft.iOS.dll:ObjCRuntime.BlockCollector..ctor(System.IntPtr) Microsoft.iOS.dll:ObjCRuntime.BlockCollector.Add(System.IntPtr) @@ -1084,7 +1072,6 @@ Microsoft.iOS.dll:System.Int32 Foundation.NSDictionary/d__66::<>1 Microsoft.iOS.dll:System.Int32 Foundation.NSDictionary/d__66::<>7__wrap2 Microsoft.iOS.dll:System.Int32 Foundation.NSObjectFlag::value__ Microsoft.iOS.dll:System.Int32 ObjCRuntime.Arch::value__ -Microsoft.iOS.dll:System.Int32 ObjCRuntime.ArgumentSemantic::value__ Microsoft.iOS.dll:System.Int32 ObjCRuntime.BlockCollector::count Microsoft.iOS.dll:System.Int32 ObjCRuntime.Dlfcn/Mode::value__ Microsoft.iOS.dll:System.Int32 ObjCRuntime.MarshalManagedExceptionMode::value__ @@ -1265,7 +1252,6 @@ Microsoft.iOS.dll:System.Runtime.InteropServices.NFloat CoreGraphics.CGRect::x Microsoft.iOS.dll:System.Runtime.InteropServices.NFloat CoreGraphics.CGRect::y Microsoft.iOS.dll:System.Runtime.InteropServices.ObjectiveC.ObjectiveCMarshal/UnhandledExceptionPropagationHandler ObjCRuntime.Runtime/<>O::<0>__UnhandledExceptionPropagationHandler Microsoft.iOS.dll:System.String CoreFoundation.CFString::str -Microsoft.iOS.dll:System.String Foundation.ExportAttribute::selector Microsoft.iOS.dll:System.String Foundation.NSException::Name() Microsoft.iOS.dll:System.String Foundation.NSException::Reason() Microsoft.iOS.dll:System.String Foundation.NSNumber::StringValue() diff --git a/tools/assembly-preparer/AssemblyPreparer.cs b/tools/assembly-preparer/AssemblyPreparer.cs index 2de85e5e11db..4cbc8b1f911e 100644 --- a/tools/assembly-preparer/AssemblyPreparer.cs +++ b/tools/assembly-preparer/AssemblyPreparer.cs @@ -30,12 +30,16 @@ public class AssemblyPreparer : IDisposable { public string MakeReproPath { get; set; } = ""; - // The pre-trim (untrimmed) assemblies. Used during post-processing with the trimmable static - // registrar to read [ProtocolMember] attributes that the trimmer has removed. This is the complete - // set of assemblies that were fed into the trimmer (ILLink's input), so it forms a self-contained - // metadata universe separate from the post-trim assemblies. + // The original assemblies from before preparation and trimming. Used during post-processing with the + // trimmable static registrar to read selected registrar attributes that were removed during trimming. + // This is a complete metadata universe separate from the post-trim assemblies. public List PreTrimAssemblies { get; } = new List (); + public bool? TrimExportAttributes { + get => configuration.Application.TrimExportAttributes; + set => configuration.Application.TrimExportAttributes = value; + } + public RegistrarMode Registrar { get => configuration.Application.Registrar; set => configuration.Application.Registrar = value; @@ -161,10 +165,10 @@ public bool Prepare (out List exceptions) var steps = new List { // CollectAssembliesStep new LoadAssembliesStep (), - new ComputeMethodOverridesStep (), - new CoreTypeMapStep (), - new CollectFieldsStep (), // ProcessExportedFields }; + steps.Add (new ComputeMethodOverridesStep ()); + steps.Add (new CoreTypeMapStep ()); + steps.Add (new CollectFieldsStep ()); // ProcessExportedFields // These steps only do anything for assemblies that are being trimmed (their IsActiveFor requires // AssemblyAction.Link), so don't even add them to the list when nothing's being trimmed. @@ -180,16 +184,17 @@ public bool Prepare (out List exceptions) steps.Add (new InlineDlfcnMethodsStep ()); - // Only add RegistrarRemovalTrackingStep if it's needed: - // * If the user explicitly set $(DynamicRegistrationSupported), we don't need to compute the value (it's - // passed straight through to the trimmer feature switch). + // Only add DetectApiUsageStep if it's needed: + // * If the user explicitly set $(DynamicRegistrationSupported), we don't need to compute the value, but + // Export attribute removal still needs the step to detect NSXpcInterface reflection. // * If nothing is being trimmed, the dynamic registrar (which lives in the platform assembly, an SDK // assembly that's only trimmed when trimming is enabled) can't be removed, so there's nothing to compute. - if (!configuration.DynamicRegistrationSupported.HasValue && configuration.Application.AreAnyAssembliesTrimmed) - steps.Add (new RegistrarRemovalTrackingStep ()); + if (configuration.Application.AreAnyAssembliesTrimmed && (!configuration.DynamicRegistrationSupported.HasValue || configuration.Application.TrimExportAttributes != false)) + steps.Add (new DetectApiUsageStep ()); // PreMarkDispatcher: I don't think we need this one steps.Add (new ManagedRegistrarStep ()); + steps.Add (new ComputeExportAttributeRemovalStep ()); steps.Add (new TrimmableRegistrarStep ()); steps.Add (new ManagedRegistrarLookupTablesStep ()); steps.Add (new InlineClassGetHandleStep ()); @@ -198,12 +203,11 @@ public bool Prepare (out List exceptions) return RunSteps (steps, out exceptions); } - // Load the pre-trim (untrimmed) assemblies so the trimmable static registrar can read the - // [ProtocolMember] attributes the trimmer removed from the post-trim assemblies. The pre-trim - // assemblies are loaded into their own resolver (a separate, self-contained metadata universe from - // the post-trim assemblies), and stored on the Application for the registrar to consult. There's no - // fallback to the post-trim resolver: the pre-trim set is complete (it's the trimmer's input), and - // falling back would mix the two universes and resolve trimmed-away references incorrectly. + // Load the original assemblies so the trimmable static registrar can read selected attributes removed + // during trimming. The assemblies are loaded into their own resolver (a separate, self-contained + // metadata universe from the post-trim assemblies), and stored on the Application for the registrar to + // consult. There's no fallback to the post-trim resolver: the original set is complete, and falling back + // would mix the two universes and resolve trimmed-away references incorrectly. void LoadPreTrimAssemblies () { if (PreTrimAssemblies.Count == 0) @@ -297,6 +301,13 @@ bool RunSteps (IList steps, out List e exceptions.Add (ErrorHelper.CreateError (99, "RegistrarMode must be explicitly set.")); return false; } + if (Registrar != RegistrarMode.TrimmableStatic) { + if (TrimExportAttributes == true) { + exceptions.Add (ErrorHelper.CreateError (99, "Export attributes can only be trimmed with the trimmable static registrar.")); + return false; + } + TrimExportAttributes = false; + } if (!string.IsNullOrEmpty (MakeReproPath) && !SaveToReproPath (exceptions)) return false; diff --git a/tools/assembly-preparer/ComputeExportAttributeRemovalStep.cs b/tools/assembly-preparer/ComputeExportAttributeRemovalStep.cs new file mode 100644 index 000000000000..5ead9636a435 --- /dev/null +++ b/tools/assembly-preparer/ComputeExportAttributeRemovalStep.cs @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Linq; + +using Xamarin.Bundler; +using Xamarin.Linker; + +#nullable enable + +namespace Xamarin.Linker; + +public class ComputeExportAttributeRemovalStep : ConfigurationAwareStep { + protected override string Name { get; } = "ComputeExportAttributeRemoval"; + protected override int ErrorCode { get; } = 2530; + + protected override void TryProcess () + { + if (App.TrimExportAttributes == false) { + Configuration.SetOutputForMSBuild ("TrimExportAttributes", "false"); + return; + } + + var explicitlyEnabled = App.TrimExportAttributes == true; + if (App.DynamicRegistrationSupported) { + App.TrimExportAttributes = false; + Configuration.SetOutputForMSBuild ("TrimExportAttributes", "false"); + if (explicitlyEnabled) { + var (code, message) = GetDiagnostic (ExportAttributeRemovalBlocker.DynamicRegistrationSupported); + Report (ErrorHelper.CreateError (code, message)); + } + return; + } + if (App.Optimizations.OptimizeBlockLiteralSetupBlock != true) + App.TrimExportAttributesBlockers.Add (ExportAttributeRemovalBlocker.BlockLiteralSetupBlockOptimizationDisabled); + if (App.Optimizations.StaticBlockToDelegateLookup != true) + App.TrimExportAttributesBlockers.Add (ExportAttributeRemovalBlocker.StaticBlockToDelegateLookupOptimizationDisabled); + + var trimExportAttributes = App.TrimExportAttributesBlockers.Count == 0; + App.TrimExportAttributes = trimExportAttributes; + Configuration.SetOutputForMSBuild ("TrimExportAttributes", trimExportAttributes ? "true" : "false"); + + if (trimExportAttributes) + return; + + foreach (var blocker in App.TrimExportAttributesBlockers.OrderBy (v => v)) { + var (code, message) = GetDiagnostic (blocker); + var exception = explicitlyEnabled + ? ErrorHelper.CreateError (code, message) + : ErrorHelper.CreateWarning (code, message); + Report (exception); + } + } + + static (int Code, string Message) GetDiagnostic (ExportAttributeRemovalBlocker blocker) + { + return blocker switch { + ExportAttributeRemovalBlocker.DynamicRegistrationSupported => (4193, Errors.MX4193), + ExportAttributeRemovalBlocker.BlockLiteralSetupBlockOptimizationDisabled => (4194, Errors.MX4194), + ExportAttributeRemovalBlocker.StaticBlockToDelegateLookupOptimizationDisabled => (4195, Errors.MX4195), + ExportAttributeRemovalBlocker.RuntimeGetBlockWrapperCreatorRequired => (4196, Errors.MX4196), + ExportAttributeRemovalBlocker.RegistrarHelperGetBlockForDelegateRequired => (4197, Errors.MX4197), + ExportAttributeRemovalBlocker.NSXpcInterfaceMethodInfoOverloadUsed => (4198, Errors.MX4198), + _ => throw new InvalidOperationException ($"Unknown Export attribute removal blocker: {blocker}."), + }; + } +} diff --git a/tools/assembly-preparer/assembly-preparer.csproj b/tools/assembly-preparer/assembly-preparer.csproj index 69e3e12f26ec..f8800110d5b0 100644 --- a/tools/assembly-preparer/assembly-preparer.csproj +++ b/tools/assembly-preparer/assembly-preparer.csproj @@ -249,8 +249,8 @@ external/tools/linker/OptimizeGeneratedCode.cs - - external/tools/linker/RegistrarRemovalTrackingStep.cs + + external/tools/linker/DetectApiUsageStep.cs external/tools/linker/RemoveUserResourcesSubStep.cs diff --git a/tools/common/Application.cs b/tools/common/Application.cs index 27b79cd470aa..d72666d07f2d 100644 --- a/tools/common/Application.cs +++ b/tools/common/Application.cs @@ -57,6 +57,15 @@ public enum RegistrarOptions { Trace = 1, } + public enum ExportAttributeRemovalBlocker { + DynamicRegistrationSupported, + BlockLiteralSetupBlockOptimizationDisabled, + StaticBlockToDelegateLookupOptimizationDisabled, + RuntimeGetBlockWrapperCreatorRequired, + RegistrarHelperGetBlockForDelegateRequired, + NSXpcInterfaceMethodInfoOverloadUsed, + } + public partial class Application : IToolLog { public Cache? Cache; public string AppDirectory = "."; @@ -83,6 +92,8 @@ public partial class Application : IToolLog { public List? AotOtherArguments = null; public bool? AotFloat32 = null; public bool PrepareAssemblies; // True if '$(PrepareAssemblies)' == 'true' + public bool? TrimExportAttributes; + public HashSet TrimExportAttributesBlockers = new HashSet (); // The set of UnmanagedCallersOnly trampoline symbols (without the leading Mach-O underscore) // that survived the NativeAOT compiler (ILC). This is only set when the native registrar code @@ -122,10 +133,10 @@ public bool IsClassReferencedByInlinedClassGetHandle (string exportedName) #if ASSEMBLY_PREPARER public bool InCustomTrimmerStep = false; public bool IsPostProcessingAssemblies; - // When post-processing assemblies with the trimmable static registrar, the [ProtocolMember] attributes - // have been removed by the trimmer, so the registrar reads them from the pre-trim (untrimmed) assemblies - // instead. This resolver provides access to the pre-trim assemblies (a separate metadata universe from - // the post-trim assemblies), and is null when not applicable. + // When post-processing assemblies with the trimmable static registrar, selected registrar attributes + // have been removed during trimming, so the registrar reads them from the original assemblies. + // This resolver provides access to the original assemblies (a separate metadata universe from the + // post-trim assemblies), and is null when not applicable. public Mono.Cecil.IAssemblyResolver? PreTrimAssemblyResolver; #else public bool InCustomTrimmerStep = true; diff --git a/tools/common/StaticRegistrar.cs b/tools/common/StaticRegistrar.cs index be6a84f1cdc1..a3c40a26ee8a 100644 --- a/tools/common/StaticRegistrar.cs +++ b/tools/common/StaticRegistrar.cs @@ -655,25 +655,109 @@ public Xamarin.Tuner.DerivedLinkContext LinkContext { // Look for linked away attributes as well as attributes on the attribute provider. IEnumerable GetCustomAttributes (ICustomAttributeProvider provider, string @namespace, string name, bool inherits = false) { +#if ASSEMBLY_PREPARER + var found = false; +#endif #if !LEGACY_TOOLS var dict = LinkContext?.Annotations?.GetCustomAnnotations (name); if (dict?.TryGetValue (provider, out var annotations) == true) { var attributes = (IEnumerable) annotations; foreach (var attrib in attributes) { - if (IsAttributeMatch (attrib, @namespace, name, inherits)) + if (IsAttributeMatch (attrib, @namespace, name, inherits)) { +#if ASSEMBLY_PREPARER + found = true; +#endif yield return attrib; + } } } #endif if (provider.HasCustomAttributes) { foreach (var attrib in provider.CustomAttributes) { - if (IsAttributeMatch (attrib, @namespace, name, inherits)) + if (IsAttributeMatch (attrib, @namespace, name, inherits)) { +#if ASSEMBLY_PREPARER + found = true; +#endif yield return attrib; + } } } + +#if ASSEMBLY_PREPARER + if (found || !ShouldReadAttributeFromPreTrimAssembly (@namespace, name)) + yield break; + + var preTrimProvider = GetPreTrimAttributeProvider (provider); + if (preTrimProvider is null) + yield break; + foreach (var attrib in preTrimProvider.CustomAttributes) { + if (IsAttributeMatch (attrib, @namespace, name, inherits)) + yield return attrib; + } +#endif } +#if ASSEMBLY_PREPARER + bool ShouldReadAttributeFromPreTrimAssembly (string @namespace, string name) + { + if (!App.IsPostProcessingAssemblies || App.PreTrimAssemblyResolver is null) + return false; + + if (@namespace != Foundation) + return false; + + if (name == StringConstants.ProtocolMemberAttribute) + return true; + + return App.TrimExportAttributes == true && name == StringConstants.ExportAttribute; + } + + ICustomAttributeProvider? GetPreTrimAttributeProvider (ICustomAttributeProvider postTrimProvider) + { + var resolver = App.PreTrimAssemblyResolver; + if (resolver is null) + throw new InvalidOperationException ("The pre-trim assembly resolver is not available."); + + switch (postTrimProvider) { + case AssemblyDefinition assembly: + return resolver.Resolve (assembly.Name); + case ModuleDefinition module: + var preTrimAssembly = resolver.Resolve (module.Assembly.Name); + return GetSinglePreTrimProvider (preTrimAssembly.Modules, v => v.Name == module.Name, module); + case TypeDefinition type: + var preTrimModule = (ModuleDefinition?) GetPreTrimAttributeProvider (type.Module); + return preTrimModule?.GetType (type.FullName); + case MethodDefinition method: + var preTrimType = (TypeDefinition?) GetPreTrimAttributeProvider (method.DeclaringType); + return preTrimType is null ? null : GetSinglePreTrimProvider (preTrimType.Methods, v => v.FullName == method.FullName && v.GenericParameters.Count == method.GenericParameters.Count, method); + case PropertyDefinition property: + var preTrimPropertyType = (TypeDefinition?) GetPreTrimAttributeProvider (property.DeclaringType); + return preTrimPropertyType is null ? null : GetSinglePreTrimProvider (preTrimPropertyType.Properties, v => v.FullName == property.FullName, property); + case ParameterDefinition parameter: + var preTrimMethod = (MethodDefinition?) GetPreTrimAttributeProvider ((MethodDefinition) parameter.Method); + if (preTrimMethod is null || parameter.Index < 0 || parameter.Index >= preTrimMethod.Parameters.Count) + return null; + return preTrimMethod.Parameters [parameter.Index]; + case MethodReturnType returnType: + var preTrimReturnMethod = (MethodDefinition?) GetPreTrimAttributeProvider ((MethodDefinition) returnType.Method); + return preTrimReturnMethod?.MethodReturnType; + default: + throw new InvalidOperationException ($"Unable to map the post-trim custom attribute provider '{postTrimProvider}' ({postTrimProvider.GetType ().FullName}) to a pre-trim provider."); + } + } + + static T? GetSinglePreTrimProvider (IEnumerable providers, Func predicate, ICustomAttributeProvider postTrimProvider) where T : class, ICustomAttributeProvider + { + var matches = providers.Where (predicate).Take (2).ToArray (); + if (matches.Length == 1) + return matches [0]; + if (matches.Length == 0) + return null; + throw new InvalidOperationException ($"The post-trim custom attribute provider '{postTrimProvider}' maps to multiple pre-trim providers."); + } +#endif + public bool TryGetAttribute (ICustomAttributeProvider provider, string @namespace, string attributeName, [NotNullWhen (true)] out ICustomAttribute? attribute) { attribute = null; @@ -1586,18 +1670,6 @@ protected override IEnumerable GetProtocolMemberAttribu if (td is null) yield break; -#if ASSEMBLY_PREPARER - // When post-processing assemblies with the trimmable static registrar, the [ProtocolMember] - // attributes have been removed by the trimmer, so read them from the pre-trim (untrimmed) - // assemblies instead. - if (App.IsPostProcessingAssemblies && App.PreTrimAssemblyResolver is not null) { - var preTrimAssembly = App.PreTrimAssemblyResolver.Resolve (td.Module.Assembly.Name); - var preTrimType = preTrimAssembly?.MainModule.GetType (td.FullName); - if (preTrimType is not null) - td = preTrimType; - } -#endif - foreach (var ca in GetCustomAttributes (td, Foundation, StringConstants.ProtocolMemberAttribute)) { var rv = new ProtocolMemberAttribute (); @@ -1924,7 +1996,7 @@ static BindAsAttribute CreateBindAsAttribute (ICustomAttribute attrib, IMemberDe } } - public static ExportAttribute? CreateExportAttribute (IMemberDefinition candidate) + public ExportAttribute? CreateExportAttribute (IMemberDefinition candidate) { bool is_variadic = false; var attribute = GetExportAttribute (candidate); @@ -1957,16 +2029,9 @@ static BindAsAttribute CreateBindAsAttribute (ICustomAttribute attrib, IMemberDe } // [Export] is not sealed anymore - so we cannot simply compare strings - public static ICustomAttribute? GetExportAttribute (ICustomAttributeProvider candidate) + public ICustomAttribute? GetExportAttribute (ICustomAttributeProvider candidate) { - if (!candidate.HasCustomAttributes) - return null; - - foreach (CustomAttribute ca in candidate.CustomAttributes) { - if (ca.Constructor.DeclaringType.Inherits (Foundation, StringConstants.ExportAttribute)) - return ca; - } - return null; + return GetCustomAttributes (candidate, Foundation, StringConstants.ExportAttribute, inherits: true).FirstOrDefault (); } PropertyDefinition GetBasePropertyInTypeHierarchy (PropertyDefinition property) diff --git a/tools/dotnet-linker/LinkerConfiguration.cs b/tools/dotnet-linker/LinkerConfiguration.cs index 5ba1ac6a5c51..4751014e988f 100644 --- a/tools/dotnet-linker/LinkerConfiguration.cs +++ b/tools/dotnet-linker/LinkerConfiguration.cs @@ -32,8 +32,9 @@ public class LinkerConfiguration { public string CacheDirectory { get; private set; } = string.Empty; public Version? DeploymentTarget { get; private set; } // The user-provided value of the $(DynamicRegistrationSupported) MSBuild property (null if not set). - // When set, RegistrarRemovalTrackingStep doesn't need to run in the assembly-preparer. - // This is also how the value RegistrarRemovalTrackingStep computed during the preparation pass is + // When set, DetectApiUsageStep doesn't need to compute this value in the assembly-preparer, + // although it may still run to detect blockers for other optimizations. + // This is also how the value DetectApiUsageStep computed during the preparation pass is // passed to the post-processing pass (which needs it to generate the native main file). public bool? DynamicRegistrationSupported { get; set; } public HashSet FrameworkAssemblies { get; private set; } = new HashSet (); @@ -320,12 +321,12 @@ Configurator GetConfigurator (string linker_file) { "DynamicRegistrationSupported", ( // This is the user-overridable $(DynamicRegistrationSupported) MSBuild property. It maps to // the RemoveDynamicRegistrar optimization (inverted): if dynamic registration is supported, - // then we're not removing the dynamic registrar. When set, RegistrarRemovalTrackingStep doesn't - // need to run in the assembly-preparer (the value is passed straight through to the trimmer + // then we're not removing the dynamic registrar. When set, DetectApiUsageStep doesn't + // need to compute the value in the assembly-preparer (it's passed straight through to the trimmer // feature switch), and it won't recompute the value in the real linker either. new LoadValue ((key, value) => { if (string.IsNullOrEmpty (value)) - return; // Not set: RegistrarRemovalTrackingStep will compute a default value. + return; // Not set: DetectApiUsageStep will compute a default value. if (!TryParseOptionalBoolean (value, out var dynamicRegistrationSupported)) throw new InvalidOperationException ($"Unable to parse the {key} value: {value} in {linker_file}"); if (dynamicRegistrationSupported.HasValue) { @@ -646,6 +647,16 @@ Configurator GetConfigurator (string linker_file) new LoadValue ((key, value) => TrimMode = value), new SaveValue ((key, storage) => saveNonEmpty (key, TrimMode, storage)) )}, + { "TrimExportAttributes", ( + new LoadValue ((key, value) => { + if (string.IsNullOrEmpty (value)) { + Application.TrimExportAttributes = null; + } else { + loadNullableBool (key, value, out Application.TrimExportAttributes); + } + }), + new SaveValue ((key, storage) => saveNullableBool (key, Application.TrimExportAttributes, storage)) + )}, { "TypeMapAssemblyName", ( new LoadValue ((key, value) => Application.TypeMapAssemblyName = value), new SaveValue ((key, storage) => saveNonEmpty (key, Application.TypeMapAssemblyName, storage)) diff --git a/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs b/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs index bca1ac585acd..2f539ea87447 100644 --- a/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs +++ b/tools/dotnet-linker/Steps/ManagedRegistrarStep.cs @@ -1669,6 +1669,8 @@ bool EmitConversion (MethodDefinition method, ILProcessor il, TypeReference type if (toManaged) { var createMethod = StaticRegistrar.GetBlockWrapperCreator (objcMethod, parameter); if (createMethod is null) { + if (App.TrimExportAttributes != false) + App.TrimExportAttributesBlockers.Add (ExportAttributeRemovalBlocker.RuntimeGetBlockWrapperCreatorRequired); AddException (ErrorHelper.CreateWarning (App, 4174 /* Unable to locate the block to delegate conversion method for the method {0}'s parameter #{1}. */, method, Errors.MT4174, method.FullName, parameter + 1)); // var blockCopy = BlockLiteral.Copy (block); var tmpVariable = il.Body.AddVariable (abr.System_IntPtr); @@ -1725,6 +1727,8 @@ bool EmitConversion (MethodDefinition method, ILProcessor il, TypeReference type il.Emit (OpCodes.Ldstr, signature); il.Emit (OpCodes.Call, abr.BlockLiteral_CreateBlockForDelegate); } else { + if (App.TrimExportAttributes != false) + App.TrimExportAttributesBlockers.Add (ExportAttributeRemovalBlocker.RegistrarHelperGetBlockForDelegateRequired); il.Emit (OpCodes.Ldtoken, method); il.Emit (OpCodes.Call, abr.RegistrarHelper_GetBlockForDelegate); } diff --git a/tools/dotnet-linker/dotnet-linker.csproj b/tools/dotnet-linker/dotnet-linker.csproj index 9a5d2966bc7b..563b834f1c22 100644 --- a/tools/dotnet-linker/dotnet-linker.csproj +++ b/tools/dotnet-linker/dotnet-linker.csproj @@ -227,8 +227,8 @@ external/tools/common/XamarinRuntime.cs - - external/tools/linker/RegistrarRemovalTrackingStep.cs + + external/tools/linker/DetectApiUsageStep.cs diff --git a/tools/linker/RegistrarRemovalTrackingStep.cs b/tools/linker/DetectApiUsageStep.cs similarity index 89% rename from tools/linker/RegistrarRemovalTrackingStep.cs rename to tools/linker/DetectApiUsageStep.cs index 36f9e17306fb..90ee6a9f01ce 100644 --- a/tools/linker/RegistrarRemovalTrackingStep.cs +++ b/tools/linker/DetectApiUsageStep.cs @@ -3,6 +3,7 @@ using Mono.Cecil; using Mono.Cecil.Cil; using Mono.Linker; +using Mono.Tuner; using Xamarin.Bundler; using Xamarin.Linker; @@ -10,9 +11,9 @@ #nullable enable namespace MonoTouch.Tuner { - public class RegistrarRemovalTrackingStep : ConfigurationAwareStep { + public class DetectApiUsageStep : ConfigurationAwareStep { - protected override string Name { get; } = "RegistrarRemovalTracking"; + protected override string Name { get; } = "DetectApiUsage"; protected override int ErrorCode { get; } = 2380; int WarnCode => ErrorCode + 7; @@ -87,6 +88,9 @@ bool RequiresDynamicRegistrar (AssemblyDefinition assembly, bool warnIfRequired) if (name != productAssemblyName) continue; + if (App.TrimExportAttributes != false && IsNSXpcInterfaceMethodInfoOverload (mr)) + App.TrimExportAttributesBlockers.Add (ExportAttributeRemovalBlocker.NSXpcInterfaceMethodInfoOverloadUsed); + switch (mr.DeclaringType.Namespace) { case "ObjCRuntime": switch (mr.DeclaringType.Name) { @@ -155,6 +159,18 @@ bool RequiresDynamicRegistrar (AssemblyDefinition assembly, bool warnIfRequired) return requires; } + static bool IsNSXpcInterfaceMethodInfoOverload (MemberReference member) + { + if (member is not MethodReference method) + return false; + if (!method.DeclaringType.Is ("Foundation", "NSXpcInterface")) + return false; + if (method.Parameters.Count == 0 || !method.Parameters [0].ParameterType.Is ("System.Reflection", "MethodInfo")) + return false; + + return method.Name == "GetAllowedClasses" || method.Name == "SetAllowedClasses"; + } + void Warn (AssemblyDefinition assembly, MemberReference mr) { ErrorHelper.Warning (App, WarnCode, Errors.MM2107, assembly.Name.Name, mr.DeclaringType.FullName, mr.Name, string.Join (", ", ((MethodReference) mr).Parameters.Select ((v) => v.ParameterType.FullName))); diff --git a/tools/mtouch/Errors.designer.cs b/tools/mtouch/Errors.designer.cs index 8201fdecc511..77d077170643 100644 --- a/tools/mtouch/Errors.designer.cs +++ b/tools/mtouch/Errors.designer.cs @@ -3677,6 +3677,60 @@ public static string MX4191 { } } + /// + /// Looks up a localized string similar to Export attributes cannot be removed because dynamic registration is supported.. + /// + public static string MX4193 { + get { + return ResourceManager.GetString("MX4193", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Export attributes cannot be removed because the blockliteral-setupblock optimization is disabled.. + /// + public static string MX4194 { + get { + return ResourceManager.GetString("MX4194", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Export attributes cannot be removed because the static-block-to-delegate-lookup optimization is disabled.. + /// + public static string MX4195 { + get { + return ResourceManager.GetString("MX4195", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Export attributes cannot be removed because the managed registrar must use Runtime.GetBlockWrapperCreator.. + /// + public static string MX4196 { + get { + return ResourceManager.GetString("MX4196", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Export attributes cannot be removed because the managed registrar must use RegistrarHelper.GetBlockForDelegate.. + /// + public static string MX4197 { + get { + return ResourceManager.GetString("MX4197", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Export attributes cannot be removed because the application uses an NSXpcInterface overload that obtains a selector from MethodInfo.. + /// + public static string MX4198 { + get { + return ResourceManager.GetString("MX4198", resourceCulture); + } + } + /// /// Looks up a localized string similar to The native linker failed to execute: {0}. Please file a bug report at https://github.com/dotnet/macios/issues/new /// . diff --git a/tools/mtouch/Errors.resx b/tools/mtouch/Errors.resx index 9ce37f3745c1..b9ef8941c4dc 100644 --- a/tools/mtouch/Errors.resx +++ b/tools/mtouch/Errors.resx @@ -1644,6 +1644,30 @@ The registrar cannot use the model class '{0}' as a generic type argument in the generic type '{1}'. Use the protocol interface instead of the model class. + + Export attributes cannot be removed because dynamic registration is supported. + + + + Export attributes cannot be removed because the blockliteral-setupblock optimization is disabled. + + + + Export attributes cannot be removed because the static-block-to-delegate-lookup optimization is disabled. + + + + Export attributes cannot be removed because the managed registrar must use Runtime.GetBlockWrapperCreator. + + + + Export attributes cannot be removed because the managed registrar must use RegistrarHelper.GetBlockForDelegate. + + + + Export attributes cannot be removed because the application uses an NSXpcInterface overload that obtains a selector from MethodInfo. + + Missing '{0}' compiler. Please install Xcode 'Command-Line Tools' component