[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:
Родитель
22fe547944
Коммит
ac496baabb
|
@ -4,7 +4,7 @@
|
|||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<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>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -2579,8 +2579,13 @@ public partial class Generator : IMemberGatherer {
|
|||
// 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...
|
||||
// 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 ("");
|
||||
#if NET
|
||||
print ("[Preserve (Conditional = true)]");
|
||||
print ("[global::System.Diagnostics.CodeAnalysis.DynamicDependency (\"Handler\")]");
|
||||
#endif
|
||||
print ("[MonoPInvokeCallback (typeof ({0}))]", ti.DelegateName);
|
||||
print ("static unsafe {0} {1} ({2}) {{", ti.ReturnType, ti.TrampolineName, ti.Parameters);
|
||||
indent++;
|
||||
|
|
|
@ -54,9 +54,6 @@ namespace LinkAll {
|
|||
public class CommonLinkAllTest {
|
||||
string WorkAroundLinkerHeuristics { get { return ""; } }
|
||||
|
||||
#if NET
|
||||
[Ignore ("https://github.com/xamarin/xamarin-macios/issues/9562")]
|
||||
#endif
|
||||
[Test]
|
||||
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)
|
||||
{
|
||||
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.
|
||||
InsertAfter (new CollectAssembliesStep (), "LoadReferencesStep");
|
||||
|
||||
var pre_dynamic_dependency_lookup_substeps = new DotNetSubStepDispatcher ();
|
||||
InsertBefore (pre_dynamic_dependency_lookup_substeps, "DynamicDependencyLookupStep");
|
||||
|
||||
var prelink_substeps = new DotNetSubStepDispatcher ();
|
||||
InsertAfter (prelink_substeps, "RemoveSecurityStep");
|
||||
|
||||
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
|
||||
// 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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче