[dotnet-linker] Make sure to preserve block-related generated code. Fixes #9562. (#9685)

Fixes https://github.com/xamarin/xamarin-macios/issues/9562.

It also fixes this monotouch-test when enabling all optimizations:

    [FAIL] BlockReturnTest : ObjCRuntime.RuntimeException : Invalid DelegateProxyAttribute for the return value for the method MonoTouchFixtures.ObjCRuntime.RegistrarTest+BlockReturnTestClass.MethodReturningBlock: DelegateType (ObjCRuntime.Trampolines+SDRegistrarTestBlock) specifies a type without a 'Handler' field.
This commit is contained in:
Rolf Bjarne Kvinge 2020-09-25 16:57:04 +02:00 коммит произвёл GitHub
Родитель 22fe547944
Коммит ac496baabb
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
6 изменённых файлов: 211 добавлений и 4 удалений

Просмотреть файл

@ -4,7 +4,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net5.0</TargetFramework> <TargetFramework>net5.0</TargetFramework>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<DefineConstants>DEBUG;BGENERATOR;NET_4_0;NO_AUTHENTICODE;STATIC;NO_SYMBOL_WRITER</DefineConstants> <DefineConstants>DEBUG;BGENERATOR;NET_4_0;NO_AUTHENTICODE;STATIC;NO_SYMBOL_WRITER;NET</DefineConstants>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

Просмотреть файл

@ -2579,8 +2579,13 @@ public partial class Generator : IMemberGatherer {
// it can't be conditional without fixing https://github.com/mono/linker/issues/516 // it can't be conditional without fixing https://github.com/mono/linker/issues/516
// but we have a workaround in place because we can't fix old, binary bindings so... // but we have a workaround in place because we can't fix old, binary bindings so...
// print ("[Preserve (Conditional=true)]"); // print ("[Preserve (Conditional=true)]");
// For .NET we fix it using the DynamicDependency attribute below
print ("static internal readonly {0} Handler = {1};", ti.DelegateName, ti.TrampolineName); print ("static internal readonly {0} Handler = {1};", ti.DelegateName, ti.TrampolineName);
print (""); print ("");
#if NET
print ("[Preserve (Conditional = true)]");
print ("[global::System.Diagnostics.CodeAnalysis.DynamicDependency (\"Handler\")]");
#endif
print ("[MonoPInvokeCallback (typeof ({0}))]", ti.DelegateName); print ("[MonoPInvokeCallback (typeof ({0}))]", ti.DelegateName);
print ("static unsafe {0} {1} ({2}) {{", ti.ReturnType, ti.TrampolineName, ti.Parameters); print ("static unsafe {0} {1} ({2}) {{", ti.ReturnType, ti.TrampolineName, ti.Parameters);
indent++; indent++;

Просмотреть файл

@ -54,9 +54,6 @@ namespace LinkAll {
public class CommonLinkAllTest { public class CommonLinkAllTest {
string WorkAroundLinkerHeuristics { get { return ""; } } string WorkAroundLinkerHeuristics { get { return ""; } }
#if NET
[Ignore ("https://github.com/xamarin/xamarin-macios/issues/9562")]
#endif
[Test] [Test]
public void BindingsAndBeforeInitField () public void BindingsAndBeforeInitField ()
{ {

Просмотреть файл

@ -25,6 +25,17 @@ namespace Xamarin {
} }
} }
void InsertBefore (IStep step, string stepName)
{
for (int i = 0; i < Steps.Count; i++) {
if (Steps [i].GetType ().Name == stepName) {
Steps.Insert (i, step);
return;
}
}
throw new InvalidOperationException ($"Could not insert {step} before {stepName} because {stepName} wasn't found.");
}
void InsertAfter (IStep step, string stepName) void InsertAfter (IStep step, string stepName)
{ {
for (int i = 0; i < Steps.Count;) { for (int i = 0; i < Steps.Count;) {
@ -46,10 +57,15 @@ namespace Xamarin {
// This would not be needed of LinkContext.GetAssemblies () was exposed to us. // This would not be needed of LinkContext.GetAssemblies () was exposed to us.
InsertAfter (new CollectAssembliesStep (), "LoadReferencesStep"); InsertAfter (new CollectAssembliesStep (), "LoadReferencesStep");
var pre_dynamic_dependency_lookup_substeps = new DotNetSubStepDispatcher ();
InsertBefore (pre_dynamic_dependency_lookup_substeps, "DynamicDependencyLookupStep");
var prelink_substeps = new DotNetSubStepDispatcher (); var prelink_substeps = new DotNetSubStepDispatcher ();
InsertAfter (prelink_substeps, "RemoveSecurityStep"); InsertAfter (prelink_substeps, "RemoveSecurityStep");
if (Configuration.LinkMode != LinkMode.None) { if (Configuration.LinkMode != LinkMode.None) {
pre_dynamic_dependency_lookup_substeps.Add (new PreserveBlockCodeSubStep ());
// We need to run the ApplyPreserveAttribute step even we're only linking sdk assemblies, because even // We need to run the ApplyPreserveAttribute step even we're only linking sdk assemblies, because even
// though we know that sdk assemblies will never have Preserve attributes, user assemblies may have // though we know that sdk assemblies will never have Preserve attributes, user assemblies may have
// [assembly: LinkSafe] attributes, which means we treat them as sdk assemblies and those may have // [assembly: LinkSafe] attributes, which means we treat them as sdk assemblies and those may have

Просмотреть файл

@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using Mono.Linker;
using Mono.Linker.Steps;
using Xamarin.Bundler;
using Xamarin.Tuner;
namespace Xamarin.Linker {
public abstract class ConfigurationAwareSubStep : BaseSubStep {
public LinkerConfiguration Configuration { get; private set; }
public DerivedLinkContext LinkContext {
get { return Configuration.DerivedLinkContext; }
}
public override sealed void Initialize (LinkContext context)
{
base.Initialize (context);
Configuration = LinkerConfiguration.GetInstance (context);
}
protected void Report (Exception exception)
{
ErrorHelper.Show (exception);
}
protected void Report (List<Exception> exceptions)
{
// Maybe there's a better way to show errors that integrates with the linker?
// We can't just throw an exception or exit here, since there might be only warnings in the list of exceptions.
ErrorHelper.Show (exceptions);
}
}
}

Просмотреть файл

@ -0,0 +1,152 @@
using System;
using System.Linq;
using Mono.Cecil;
using Mono.Linker.Steps;
using Mono.Tuner;
using Xamarin.Bundler;
namespace Xamarin.Linker.Steps {
public class PreserveBlockCodeSubStep : ConfigurationAwareSubStep {
MethodDefinition ctor_string_def;
MethodReference ctor_string_ref;
public override SubStepTargets Targets {
get {
return SubStepTargets.Assembly |
SubStepTargets.Field |
SubStepTargets.Type;
}
}
MethodReference GetConstructorReference (AssemblyDefinition assembly)
{
if (ctor_string_def == null) {
// Find the method definition for the constructor we want to use
foreach (var asm in Configuration.Assemblies) {
var dependencyAttribute = asm.MainModule.GetType ("System.Diagnostics.CodeAnalysis.DynamicDependencyAttribute");
if (dependencyAttribute == null)
continue;
foreach (var method in dependencyAttribute.Methods) {
if (!method.HasParameters)
continue;
if (method.Parameters.Count == 1 && method.Parameters [0].ParameterType.Is ("System", "String")) {
ctor_string_def = method;
break;
}
}
break;
}
if (ctor_string_def == null)
throw ErrorHelper.CreateError (99, Errors.MX0099, "Could not find the constructor 'System.Diagnostics.CodeAnalysis.DynamicDependencyAttribute..ctor(System.String)'");
}
// Import the constructor into the current assembly if it hasn't already been imported
ctor_string_ref ??= assembly.MainModule.ImportReference (ctor_string_def);
return ctor_string_ref;
}
public override void ProcessAssembly (AssemblyDefinition assembly)
{
// Clear out the method reference we have, so that we import the method definition again
ctor_string_ref = null;
base.ProcessAssembly (assembly);
}
public override void ProcessField (FieldDefinition field)
{
base.ProcessField (field);
PreserveBlockField (field);
}
void PreserveBlockField (FieldDefinition field)
{
/* 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. Unfortunately there's no programmatic way
to preserve a field dependent upon the preservation of the
containing type, so we have to inject a DynamicDependency
attribute. And since we can't add a DynamicDependency attribute on
the type itself, we add it to the Invoke method. We also need to
preserve the Invoke method (which is done programmatically). Our
generator generates the required attributes, but since we have to
work with existing assemblies, we detect the scenario here as well
and inject the attributes manually if they're not already there.
*/
// First make sure we got the right field
// The containing type for the field we're looking for is abstract, sealed and nested and contains exactly 1 field.
var td = field.DeclaringType;
if (!td.IsAbstract || !td.IsSealed || !td.IsNested)
return;
if (td.Fields.Count != 1)
return;
// The containing type is also nested inside ObjCRuntime.Trampolines class)
var nestingType = td.DeclaringType;
if (!nestingType.Is ("ObjCRuntime", "Trampolines"))
return;
// The field itself is a readonly field named 'Handler'
if (!field.IsInitOnly)
return;
if (field.Name != "Handler")
return;
// One problem is that we can't add the DynamicDependency attribute to the type, nor the field itself,
// so we add it to the Invoke method in the same type.
if (!td.HasMethods)
return;
var method = td.Methods.SingleOrDefault (v => {
if (v.Name != "Invoke")
return false;
if (v.Parameters.Count == 0)
return false;
if (!v.HasCustomAttributes)
return false;
if (!v.CustomAttributes.Any (v => v.AttributeType.Name == "MonoPInvokeCallbackAttribute"))
return false;
return true;
});
if (method == null)
return;
// We need to preserve the method, if the type is used (unless it's already preserved)
if (!method.CustomAttributes.Any (v => v.AttributeType.Name == "PreserveAttribute"))
Annotations.AddPreservedMethod (method.DeclaringType, method);
// Does the method already have a DynamicDependency attribute? If so, no need to add another one
if (method.CustomAttributes.Any (v => v.AttributeType.Is ("System.Diagnostics.CodeAnalysis", "DynamicDependencyAttribute")))
return;
// Create and add the DynamicDependency attribute to the method
var ctor = GetConstructorReference (field.DeclaringType.Module.Assembly);
var attrib = new CustomAttribute (ctor);
attrib.ConstructorArguments.Add (new CustomAttributeArgument (ctor.Parameters [0].ParameterType, "Handler"));
method.CustomAttributes.Add (attrib);
}
}
}