Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion dotnet/targets/Xamarin.Shared.Sdk.targets
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,11 @@

<Warning Text="All assemblies must be processed by the linker when using NativeAOT. Please don't set neither the '$(_LinkModeProperty)' nor the 'TrimMode' property, so that the build can default to linking all assemblies." Condition="'$(_UseNativeAot)' == 'true' And '$(_LinkMode)' != 'Full'" />

<PropertyGroup>
<_UseDynamicDependenciesInsteadOfMarking Condition="'$(_UseDynamicDependenciesInsteadOfMarking)' == ''">true</_UseDynamicDependenciesInsteadOfMarking>
<_UseDynamicDependenciesForProtocolPreservation Condition="'$(_UseDynamicDependenciesForProtocolPreservation)' == ''">$(_UseDynamicDependenciesInsteadOfMarking)</_UseDynamicDependenciesForProtocolPreservation>
</PropertyGroup>

<PropertyGroup>
<!-- Yep, we want to run ILLink as well, because we need our custom steps to run (NativeAOT sets this to false, so set it back to true) -->
<RunILLink Condition="'$(PublishAot)' == 'true'">true</RunILLink>
Expand Down Expand Up @@ -734,6 +739,7 @@
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.CollectAssembliesStep" />
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="MonoTouch.Tuner.CoreTypeMapStep" />
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="MonoTouch.Tuner.ProcessExportedFields" />
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.PreserveProtocolsStep" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForProtocolPreservation)' == 'true'" />
<!-- The final decision to remove/keep the dynamic registrar must be done before the linking step -->
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="MonoTouch.Tuner.RegistrarRemovalTrackingStep" />
<!-- TODO: these steps should probably run after mark. -->
Expand All @@ -746,7 +752,7 @@
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true'" Type="Xamarin.Linker.Steps.PreserveBlockCodeHandler" />
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true'" Type="Xamarin.Linker.OptimizeGeneratedCodeHandler" />
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true'" Type="Xamarin.Linker.BackingFieldDelayHandler" />
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true'" Type="Xamarin.Linker.MarkIProtocolHandler" />
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForProtocolPreservation)' != 'true'" Type="Xamarin.Linker.MarkIProtocolHandler" />
<!-- MarkDispatcher substeps will run for all marked assemblies. -->
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true'" Type="Xamarin.Linker.Steps.MarkDispatcher" />
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true'" Type="Xamarin.Linker.Steps.PreserveSmartEnumConversionsHandler" />
Expand Down
2 changes: 2 additions & 0 deletions src/Foundation/ExportAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@

namespace Foundation {

#if !BUNDLER
/// <summary>Exports a method or property to the Objective-C world.</summary>
/// <remarks>
/// <para>
Expand All @@ -52,6 +53,7 @@ namespace Foundation {
/// ]]></code>
/// </example>
/// </remarks>
#endif
[AttributeUsage (AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property)]
public class ExportAttribute : Attribute {
string? selector;
Expand Down
2 changes: 1 addition & 1 deletion tools/common/StringUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ public static Version ParseVersion (string v)
/// <param name="code">The code of the message.</param>
/// <param name="message">The message text.</param>
/// <returns></returns>
/// <see cref="https://learn.microsoft.com/visualstudio/msbuild/msbuild-diagnostic-format-for-tasks"/>
/// <see href="https://learn.microsoft.com/visualstudio/msbuild/msbuild-diagnostic-format-for-tasks"/>
public static string FormatMessage (string? fileName, long? lineNumber, bool isError, string prefix, int code, string message)
{
var sb = new StringBuilder ();
Expand Down
144 changes: 139 additions & 5 deletions tools/dotnet-linker/AppBundleRewriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

using Xamarin.Bundler;
using Xamarin.Linker;
using Xamarin.Utils;

#nullable enable

Expand Down Expand Up @@ -1247,18 +1248,20 @@ public void ClearCurrentAssembly ()

public CustomAttribute CreateDynamicDependencyAttribute (string memberSignature, TypeDefinition type)
{
if (type.HasGenericParameters) {
var typeName = Xamarin.Utils.DocumentationComments.GetSignature (type);
var assemblyName = type.Module.Assembly.Name.Name;
return CreateDynamicDependencyAttribute (memberSignature, typeName, assemblyName);
}
if (type.HasGenericParameters)
return CreateDynamicDependencyAttribute (memberSignature, type, type.Module.Assembly);

var attribute = new CustomAttribute (DynamicDependencyAttribute_ctor__String_Type);
attribute.ConstructorArguments.Add (new CustomAttributeArgument (System_String, memberSignature));
attribute.ConstructorArguments.Add (new CustomAttributeArgument (System_Type, type));
return attribute;
}

public CustomAttribute CreateDynamicDependencyAttribute (string memberSignature, TypeDefinition type, AssemblyDefinition assembly)
{
return CreateDynamicDependencyAttribute (memberSignature, DocumentationComments.GetSignature (type), assembly.Name.Name);
}

public CustomAttribute CreateDynamicDependencyAttribute (string memberSignature, string typeName, string assemblyName)
{
var attribute = new CustomAttribute (DynamicDependencyAttribute_ctor__String_String_String);
Expand All @@ -1276,5 +1279,136 @@ public CustomAttribute CreateDynamicDependencyAttribute (DynamicallyAccessedMemb
attribute.ConstructorArguments.Add (new CustomAttributeArgument (System_Type, type));
return attribute;
}

/// <summary>
/// Preserve a field conditionally on another type
/// </summary>
/// <param name="onType">The type on which to add the dynamic dependency attribute.</param>
/// <param name="forField">The field that is the target of the dynamic dependency.</param>
/// <returns>Whether an attribute was added or not.</returns>
public bool AddDynamicDependencyAttributeToStaticConstructor (TypeDefinition onType, FieldDefinition forField)
{
var attrib = CreateDynamicDependencyAttribute (DocumentationComments.GetSignature (forField), forField.DeclaringType, forField.Module.Assembly);
return AddAttributeToStaticConstructor (onType, attrib);
}

/// <summary>
/// Preserve a type conditionally on another type (if that other type is marked)
/// </summary>
/// <remarks>
/// <para>
/// Unfortunately a DynamicDependency attribute can't point to a type, only a member within a type.
/// So we add a placeholder member within the target type, and point the DynamicDependency attribute to that member.
/// </para>
/// <para>The caller is responsible for making sure the current assenbly is saved if there were any changes.</para>
/// </remarks>
/// <param name="onType">The type on which to add the dynamic dependency attribute.</param>
/// <param name="forType">The type that is the target of the dynamic dependency.</param>
/// <returns>Whether an attribute was added or not.</returns>
public bool AddDynamicDependencyAttributeToStaticConstructor (TypeDefinition onType, TypeDefinition forType)
{
var placeholderName = "__linker_preserve__";
FieldDefinition? placeholderMember = null;
if (forType.HasFields)
placeholderMember = forType.Fields.FirstOrDefault (f => f.Name == placeholderName && f.IsStatic);
if (placeholderMember is null) {
placeholderMember = new FieldDefinition (placeholderName, FieldAttributes.Private | FieldAttributes.Static, System_Int32);
forType.Fields.Add (placeholderMember);
}
return AddDynamicDependencyAttributeToStaticConstructor (onType, placeholderMember);
}

public bool AddAttributeToStaticConstructor (TypeDefinition onType, CustomAttribute attribute)
{
var cctor = GetOrCreateStaticConstructor (onType, out var modified);
modified |= AddAttributeOnlyOnce (cctor, attribute);

// Remove the BeforeFieldInit attribute from the type, otherwise the linker may trim away the static constructor, and taking our attributes with it.
if (onType.Attributes.HasFlag (TypeAttributes.BeforeFieldInit)) {
onType.Attributes &= ~TypeAttributes.BeforeFieldInit;
modified = true;
}

return modified;
}

MethodDefinition GetOrCreateStaticConstructor (TypeDefinition type, out bool modified)
{
modified = false;

var staticCtor = type.GetTypeConstructor ();
if (staticCtor is null) {
staticCtor = type.AddMethod (".cctor", MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.RTSpecialName | MethodAttributes.SpecialName | MethodAttributes.Static, System_Void);
staticCtor.CreateBody (out var il);
il.Emit (OpCodes.Ret);

modified = true;
}

return staticCtor;
}

/// <summary>
/// Add the given attribute to the provider, but only if an attribute with the same constructor and the same arguments isn't already present.
/// This is needed because we may add the same dynamic dependency attribute multiple times (for example if multiple methods call the same method that needs to be preserved),
/// and we don't want to end up with multiple copies of the same attribute, which would cause warnings in the linker.
/// </summary>
/// <param name="provider">The provider to which the attribute should be added.</param>
/// <param name="attribute">The attribute to add.</param>
/// <returns>Whether the attribute was added or not.</returns>
bool AddAttributeOnlyOnce (ICustomAttributeProvider provider, CustomAttribute attribute)
{
if (provider.HasCustomAttributes) {
foreach (var ca in provider.CustomAttributes) {
if (ca.Constructor == attribute.Constructor) {
// ok so far
} else if (ca.Constructor.DeclaringType.FullName != attribute.Constructor.DeclaringType.FullName) {
continue;
} else if (ca.Constructor.FullName != attribute.Constructor.FullName) {
continue;
}

if (ca.ConstructorArguments.Count != attribute.ConstructorArguments.Count)
continue;

if (ca.Properties.Count != attribute.Properties.Count)
continue;

var allMatch = true;
for (int i = 0; i < ca.ConstructorArguments.Count; i++) {
var caArg = ca.ConstructorArguments [i];
var attrArg = attribute.ConstructorArguments [i];
if (!object.Equals (caArg.Value, attrArg.Value)) {
allMatch = false;
break;
}
}
if (!allMatch)
continue;

for (int i = 0; i < ca.Properties.Count; i++) {
var caProp = ca.Properties [i];
var attrProp = attribute.Properties [i];

if (caProp.Name != attrProp.Name) {
allMatch = false;
break;
}

if (!object.Equals (caProp.Argument.Value, attrProp.Argument.Value)) {
allMatch = false;
break;
}
}
if (!allMatch)
continue;

// attribute already present
return false;
}
}
provider.CustomAttributes.Add (attribute);
return true;
}
}
}
17 changes: 17 additions & 0 deletions tools/dotnet-linker/Compat.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,23 @@ public bool IsProductAssembly (AssemblyDefinition assembly)
return assembly.Name.Name == Configuration.PlatformAssembly;
}

public bool ReferencesProductAssembly (AssemblyDefinition assembly)
{
if (!assembly.MainModule.HasAssemblyReferences)
return false;

foreach (var reference in assembly.MainModule.AssemblyReferences) {
if (reference.Name == Configuration.PlatformAssembly)
return true;
}
return false;
}

public bool IsOrReferencesProductAssembly (AssemblyDefinition assembly)
{
return IsProductAssembly (assembly) || ReferencesProductAssembly (assembly);
}

public bool IsSdkAssembly (AssemblyDefinition assembly)
{
return Configuration.FrameworkAssemblies.Contains (Assembly.GetIdentity (assembly));
Expand Down
81 changes: 81 additions & 0 deletions tools/dotnet-linker/PreserveProtocolsStep.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using System;

using Mono.Cecil;
using Mono.Linker;
using Mono.Linker.Steps;

using Xamarin.Linker.Steps;
using Xamarin.Tuner;

#nullable enable

namespace Xamarin.Linker {

// If we're using the dynamic registrar, we need to mark interfaces that represent protocols
// even if it doesn't look like the interfaces are used, since we need them at runtime.
//
// Sadly, there doesn't seem to be a way to preserve only some interfaces, we have to preserve them all.
// We add this to the current type's static cctor:
//
// [DynamicDependency (DynamicallyAccessedMemberTypes.Interfaces, typeof (TheClass))]
// static TheClass ()
// {
// }
//
// This step is a replacement for the MarkIProtocolHandler step (which can't be moved out of the linker).
//
// One difference with the MarkIProtocolHandler step is that this step will preserve all interfaces for
// a type, while MarkIProtocolHandler will only preserve protocol interfaces. Taking into account that
// preserving protocol interfaces is only needed when using the dynamic registrar, and that's not our
// most optimized build configuration (nor the default release configuration on any platform), this should
// hopefully not be a big issue).
//
public class PreserveProtocolsStep : AssemblyModifierStep {
protected override string Name { get; } = "Preserve Block Code";
protected override int ErrorCode { get; } = 2240;

protected override bool IsActiveFor (AssemblyDefinition assembly)
{
if (DerivedLinkContext.App.Registrar != Bundler.RegistrarMode.Dynamic)
return false;

if (Annotations.GetAction (assembly) != AssemblyAction.Link)
return false;

if (!assembly.MainModule.HasTypes)
return false;

// Unless an assembly is or references our platform assembly, then it won't have anything we need to register
if (!Configuration.Profile.IsOrReferencesProductAssembly (assembly))
return false;

return true;
}

protected override bool ProcessType (TypeDefinition type)
{
var modified = false;

if (!type.HasInterfaces)
return modified;

if (!type.IsNSObject (DerivedLinkContext))
return modified;

var hasProtocols = false;
foreach (var iface in type.Interfaces) {
var resolvedInterfaceType = iface.InterfaceType.Resolve ();
hasProtocols = resolvedInterfaceType.HasCustomAttribute (DerivedLinkContext, Namespaces.Foundation, "ProtocolAttribute");
if (hasProtocols)
break;
}

if (!hasProtocols)
return modified;

var attrib = abr.CreateDynamicDependencyAttribute (DynamicallyAccessedMemberTypes.Interfaces, type);
modified |= abr.AddAttributeToStaticConstructor (type, attrib);
return modified;
}
}
}
47 changes: 47 additions & 0 deletions tools/dotnet-linker/Steps/AssemblyModifierStep.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System;
using System.Linq;

using Mono.Cecil;

using Mono.Linker;
using Mono.Linker.Steps;
using Mono.Tuner;

using Xamarin.Bundler;
using Xamarin.Linker;

#nullable enable

namespace Xamarin.Linker.Steps;

public abstract class AssemblyModifierStep : ConfigurationAwareStep {
private protected AppBundleRewriter abr => Configuration.AppBundleRewriter;

protected override void TryProcessAssembly (AssemblyDefinition assembly)
{
var modified = false;

abr.SetCurrentAssembly (assembly);
foreach (var type in assembly.MainModule.Types)
modified |= ProcessTypeImpl (type);

if (modified)
abr.SaveCurrentAssembly ();
abr.ClearCurrentAssembly ();
}

protected virtual bool ProcessType (TypeDefinition type)
{
return false;
}

bool ProcessTypeImpl (TypeDefinition type)
{
var modified = ProcessType (type);
if (type.HasNestedTypes) {
foreach (var nested in type.NestedTypes)
modified |= ProcessTypeImpl (nested);
}
return modified;
}
}
Loading
Loading