|
| 1 | +using System; |
| 2 | +using System.Collections.Generic; |
| 3 | +using System.Linq; |
| 4 | + |
| 5 | +using Mono.Cecil; |
| 6 | + |
| 7 | +using Java.Interop.Tools.Cecil; |
| 8 | + |
| 9 | +using Mono.Linker; |
| 10 | +using Mono.Linker.Steps; |
| 11 | + |
| 12 | +using Mono.Cecil.Cil; |
| 13 | + |
| 14 | +namespace MonoDroid.Tuner |
| 15 | +{ |
| 16 | + public class AddKeepAlivesStep : BaseStep |
| 17 | + { |
| 18 | + readonly TypeDefinitionCache cache; |
| 19 | + |
| 20 | + public AddKeepAlivesStep (TypeDefinitionCache cache) |
| 21 | + { |
| 22 | + this.cache = cache; |
| 23 | + } |
| 24 | + |
| 25 | + protected override void ProcessAssembly (AssemblyDefinition assembly) |
| 26 | + { |
| 27 | + var action = Annotations.HasAction (assembly) ? Annotations.GetAction (assembly) : AssemblyAction.Skip; |
| 28 | + if (action == AssemblyAction.Delete) |
| 29 | + return; |
| 30 | + |
| 31 | + if (AddKeepAlives (assembly)) { |
| 32 | + if (action == AssemblyAction.Skip || action == AssemblyAction.Copy) |
| 33 | + Annotations.SetAction (assembly, AssemblyAction.Save); |
| 34 | + } |
| 35 | + } |
| 36 | + |
| 37 | + internal bool AddKeepAlives (AssemblyDefinition assembly) |
| 38 | + { |
| 39 | + if (!assembly.MainModule.HasTypeReference ("Java.Lang.Object")) |
| 40 | + return false; |
| 41 | + |
| 42 | + bool changed = false; |
| 43 | + List<TypeDefinition> types = assembly.MainModule.Types.ToList (); |
| 44 | + foreach (TypeDefinition type in assembly.MainModule.Types) |
| 45 | + AddNestedTypes (types, type); |
| 46 | + |
| 47 | + foreach (TypeDefinition type in types) |
| 48 | + if (MightNeedFix (type)) |
| 49 | + changed |= AddKeepAlives (type); |
| 50 | + |
| 51 | + return changed; |
| 52 | + } |
| 53 | + |
| 54 | + // Adapted from `MarkJavaObjects` |
| 55 | + static void AddNestedTypes (List<TypeDefinition> types, TypeDefinition type) |
| 56 | + { |
| 57 | + if (!type.HasNestedTypes) |
| 58 | + return; |
| 59 | + |
| 60 | + foreach (var t in type.NestedTypes) { |
| 61 | + types.Add (t); |
| 62 | + AddNestedTypes (types, t); |
| 63 | + } |
| 64 | + } |
| 65 | + |
| 66 | + bool MightNeedFix (TypeDefinition type) |
| 67 | + { |
| 68 | + return !type.IsAbstract && type.IsSubclassOf ("Java.Lang.Object", cache); |
| 69 | + } |
| 70 | + |
| 71 | + MethodDefinition methodKeepAlive = null; |
| 72 | + |
| 73 | + bool AddKeepAlives (TypeDefinition type) |
| 74 | + { |
| 75 | + bool changed = false; |
| 76 | + foreach (MethodDefinition method in type.Methods) { |
| 77 | + if (!method.CustomAttributes.Any (a => a.AttributeType.FullName == "Android.Runtime.RegisterAttribute")) |
| 78 | + continue; |
| 79 | + |
| 80 | + if (method.Parameters.Count == 0) |
| 81 | + continue; |
| 82 | + |
| 83 | + var processor = method.Body.GetILProcessor (); |
| 84 | + var module = method.DeclaringType.Module; |
| 85 | + var instructions = method.Body.Instructions; |
| 86 | + var end = instructions.Last (); |
| 87 | + if (end.Previous.OpCode == OpCodes.Endfinally) |
| 88 | + end = end.Previous; |
| 89 | + |
| 90 | + var found = false; |
| 91 | + for (int off = Math.Max (0, instructions.Count - 6); off < instructions.Count; off++) { |
| 92 | + var current = instructions [off]; |
| 93 | + if (current.OpCode == OpCodes.Call && current.Operand.ToString ().Contains ("System.GC::KeepAlive")) { |
| 94 | + found = true; |
| 95 | + break; |
| 96 | + } |
| 97 | + } |
| 98 | + |
| 99 | + if (found) |
| 100 | + continue; |
| 101 | + |
| 102 | + for (int i = 0; i < method.Parameters.Count; i++) { |
| 103 | + if (method.Parameters [i].ParameterType.IsValueType || method.Parameters [i].ParameterType.FullName == "System.String") |
| 104 | + continue; |
| 105 | + |
| 106 | + changed = true; |
| 107 | + |
| 108 | + if (methodKeepAlive == null) |
| 109 | + methodKeepAlive = Context.GetMethod ("mscorlib", "System.GC", "KeepAlive", new string [] { "System.Object" }); |
| 110 | + |
| 111 | + processor.InsertBefore (end, GetLoadArgumentInstruction (method.IsStatic ? i : i + 1, method.Parameters [i])); |
| 112 | + processor.InsertBefore (end, Instruction.Create (OpCodes.Call, module.ImportReference (methodKeepAlive))); |
| 113 | + } |
| 114 | + } |
| 115 | + return changed; |
| 116 | + } |
| 117 | + |
| 118 | + // Adapted from src/Mono.Android.Export/Mono.CodeGeneration/CodeArgumentReference.cs |
| 119 | + static Instruction GetLoadArgumentInstruction (int argNum, ParameterDefinition parameter) |
| 120 | + { |
| 121 | + switch (argNum) { |
| 122 | + case 0: return Instruction.Create (OpCodes.Ldarg_0); |
| 123 | + case 1: return Instruction.Create (OpCodes.Ldarg_1); |
| 124 | + case 2: return Instruction.Create (OpCodes.Ldarg_2); |
| 125 | + case 3: return Instruction.Create (OpCodes.Ldarg_3); |
| 126 | + default: return Instruction.Create (OpCodes.Ldarg, parameter); |
| 127 | + } |
| 128 | + } |
| 129 | + } |
| 130 | +} |
0 commit comments