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
4 changes: 3 additions & 1 deletion dotnet/targets/Xamarin.Shared.Sdk.targets
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,7 @@
<_UseDynamicDependenciesInsteadOfMarking Condition="'$(_UseDynamicDependenciesInsteadOfMarking)' == ''">true</_UseDynamicDependenciesInsteadOfMarking>
<_UseDynamicDependenciesForProtocolPreservation Condition="'$(_UseDynamicDependenciesForProtocolPreservation)' == ''">$(_UseDynamicDependenciesInsteadOfMarking)</_UseDynamicDependenciesForProtocolPreservation>
<_UseDynamicDependenciesForSmartEnumPreservation Condition="'$(_UseDynamicDependenciesForSmartEnumPreservation)' == ''">$(_UseDynamicDependenciesInsteadOfMarking)</_UseDynamicDependenciesForSmartEnumPreservation>
<_UseDynamicDependenciesForBlockCodePreservation Condition="'$(_UseDynamicDependenciesForBlockCodePreservation)' == ''">$(_UseDynamicDependenciesInsteadOfMarking)</_UseDynamicDependenciesForBlockCodePreservation>
</PropertyGroup>

<PropertyGroup>
Expand Down Expand Up @@ -752,6 +753,7 @@
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="MonoTouch.Tuner.ProcessExportedFields" />
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.PreserveProtocolsStep" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForProtocolPreservation)' == 'true'" />
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.PreserveSmartEnumConversionsStep" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForSmartEnumPreservation)' == 'true'" />
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.PreserveBlockCodeStep" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForBlockCodePreservation)' == '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 @@ -761,7 +763,7 @@
<!--
IMarkHandlers which run during Mark
-->
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true'" Type="Xamarin.Linker.Steps.PreserveBlockCodeHandler" />
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForBlockCodePreservation)' != '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' And '$(_UseDynamicDependenciesForProtocolPreservation)' != 'true'" Type="Xamarin.Linker.MarkIProtocolHandler" />
Expand Down
11 changes: 11 additions & 0 deletions tools/dotnet-linker/AppBundleRewriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1318,6 +1318,17 @@ public CustomAttribute CreateDynamicDependencyAttribute (DynamicallyAccessedMemb
return attribute;
}

/// <summary>
/// Preserve a method conditionally on another type
/// </summary>
/// <param name="onType">The type on which to add the dynamic dependency attribute.</param>
/// <param name="forMethod">The method that is the target of the dynamic dependency.</param>
public bool AddDynamicDependencyAttributeToStaticConstructor (TypeDefinition onType, MethodDefinition forMethod)
{
var attrib = CreateDynamicDependencyAttribute (DocumentationComments.GetSignature (forMethod), forMethod.DeclaringType, forMethod.Module.Assembly);
return AddAttributeToStaticConstructor (onType, attrib);
}

/// <summary>
/// Preserve a field conditionally on another type
/// </summary>
Expand Down
55 changes: 2 additions & 53 deletions tools/dotnet-linker/Steps/PreserveBlockCodeHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,62 +26,11 @@ public override void Initialize (LinkContext context, MarkContext markContext)

protected override void Process (TypeDefinition type)
{
/* For the following class:
static internal class SDInnerBlock {
// this field is not preserved by other means, but it must not be linked away
static internal readonly DInnerBlock Handler = Invoke;
[MonoPInvokeCallback (typeof (DInnerBlock))]
static internal void Invoke (IntPtr block, int magic_number)
{
}
}
We need to make sure the linker doesn't remove the Handler field
and the Invoke method.
*/

// First make sure we got the right class
// The type for the field we're looking for is abstract, sealed and nested and contains exactly 1 field.
if (!type.HasFields || !type.IsAbstract || !type.IsSealed || !type.IsNested)
return;
if (type.Fields.Count != 1)
return;

// The type is also nested inside ObjCRuntime.Trampolines class)
var nestingType = type.DeclaringType;
if (!nestingType.Is ("ObjCRuntime", "Trampolines"))
return;

// The class has a readonly field named 'Handler'
var field = type.Fields [0];
if (!field.IsInitOnly)
return;
if (field.Name != "Handler")
return;

// The class has a parameterless 'Invoke' method with a 'MonoPInvokeCallback' attribute
if (!type.HasMethods)
if (!PreserveBlockCodeStep.GetMembersToPreserve (type, out var field, out var method))
return;
var method = type.Methods.SingleOrDefault (v => {
if (v.Name != "Invoke")
return false;
if (!v.HasParameters)
return false;
if (!v.HasCustomAttributes)
return false;
if (!v.CustomAttributes.Any (v => v.AttributeType.Name == "MonoPInvokeCallbackAttribute"))
return false;
return true;
});

if (method is null)
return;

// The type was used, so preserve the method and field
Context.Annotations.Mark (method);
Context.Annotations.Mark (field);
Context.Annotations.Mark (method);
}
}
}
107 changes: 107 additions & 0 deletions tools/dotnet-linker/Steps/PreserveBlockCodeStep.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
using System;
using System.Linq;

using Mono.Cecil;

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

using Xamarin.Bundler;

#nullable enable

namespace Xamarin.Linker.Steps {

public class PreserveBlockCodeStep : AssemblyModifierStep {

protected override string Name { get; } = "Preserve Block Code";
protected override int ErrorCode { get; } = 2240;

protected override bool IsActiveFor (AssemblyDefinition assembly)
{
// We only care about assemblies that are being linked.
if (Annotations.GetAction (assembly) != AssemblyAction.Link)
return false;

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

return true;
}

protected override bool ProcessType (TypeDefinition type)
{
if (!GetMembersToPreserve (type, out var field, out var method))
return false;

var modified = false;
modified |= abr.AddDynamicDependencyAttributeToStaticConstructor (type, field);
modified |= abr.AddDynamicDependencyAttributeToStaticConstructor (type, method);
return modified;
}

public static bool GetMembersToPreserve (TypeDefinition type, [NotNullWhen (true)] out FieldDefinition? field, [NotNullWhen (true)] out MethodDefinition? method)
{
field = null;
method = null;

/* For the following class:

static internal class SDInnerBlock {
// this field is not preserved by other means, but it must not be linked away
static internal readonly DInnerBlock Handler = Invoke;

[MonoPInvokeCallback (typeof (DInnerBlock))]
static internal void Invoke (IntPtr block, int magic_number)
{
}
}

* We need to make sure the linker doesn't remove the Handler field
* and the Invoke method.
*/

// First make sure we got the right class
// The type for the field we're looking for is abstract, sealed and nested and contains exactly 1 field.
if (!type.HasFields || !type.IsAbstract || !type.IsSealed || !type.IsNested)
return false;
if (type.Fields.Count != 1)
return false;

// The type is also nested inside ObjCRuntime.Trampolines class)
var nestingType = type.DeclaringType;
if (!nestingType.Is ("ObjCRuntime", "Trampolines"))
return false;

// The class has a readonly field named 'Handler'
field = type.Fields [0];
if (!field.IsInitOnly)
return false;
if (field.Name != "Handler")
return false;

// The class has a parameterless 'Invoke' method with a 'MonoPInvokeCallback' attribute
if (!type.HasMethods)
return false;
method = type.Methods.SingleOrDefault (v => {
if (v.Name != "Invoke")
return false;
if (!v.HasParameters)
return false;
if (!v.HasCustomAttributes)
return false;
if (!v.CustomAttributes.Any (v => v.AttributeType.Name == "MonoPInvokeCallbackAttribute"))
return false;
return true;
});

if (method is null)
return false;

// The type was used, so preserve the method and field
return true;
}
}
}
Loading