Skip to content

Commit f565669

Browse files
authored
[XABT] Break Cecil usage out of TypeMapGenerator. (#9996)
In preparation for future changes to the typemap generation process, this commit refactors the usage of `Mono.Cecil` out of the `TypeMapGenerator` class. (Part of the "move `GenerateJavaStubs` to linker steps" effort.) This facilitates a future state where: - The `Module[Debug|Release]Data` entries are created from `Mono.Cecil` data in the `LinkAssembliesNoShrink`/`ILLink` step and persisted to disk. - The `GenerateTypeMappings` task reads the persisted information to generate the typemap. This refactor could be done as part of that future change, but seemed like a smaller change that could be more easily reviewed and committed separately.
1 parent 9121bea commit f565669

File tree

2 files changed

+259
-219
lines changed

2 files changed

+259
-219
lines changed
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
#nullable enable
2+
using System;
3+
using System.Collections.Generic;
4+
using System.IO;
5+
using System.Text;
6+
using Java.Interop.Tools.Cecil;
7+
using Mono.Cecil;
8+
9+
using ModuleDebugData = Xamarin.Android.Tasks.TypeMapGenerator.ModuleDebugData;
10+
using ModuleReleaseData = Xamarin.Android.Tasks.TypeMapGenerator.ModuleReleaseData;
11+
using ReleaseGenerationState = Xamarin.Android.Tasks.TypeMapGenerator.ReleaseGenerationState;
12+
using TypeMapDebugEntry = Xamarin.Android.Tasks.TypeMapGenerator.TypeMapDebugEntry;
13+
using TypeMapReleaseEntry = Xamarin.Android.Tasks.TypeMapGenerator.TypeMapReleaseEntry;
14+
15+
namespace Xamarin.Android.Tasks;
16+
17+
// Converts types from Mono.Cecil to the format used by the typemap generator.
18+
class TypeMapCecilAdapter
19+
{
20+
const string TypemapExtension = ".typemap";
21+
22+
public static Dictionary<string, ModuleDebugData> GetDebugModules (NativeCodeGenState state, string typemapFilesOutputDirectory, Encoding outputEncoding, out int maxModuleFileNameWidth)
23+
{
24+
var modules = new Dictionary<string, ModuleDebugData> (StringComparer.Ordinal);
25+
maxModuleFileNameWidth = 0;
26+
int maxModuleNameWidth = 0;
27+
28+
var javaDuplicates = new Dictionary<string, List<TypeMapDebugEntry>> (StringComparer.Ordinal);
29+
foreach (TypeDefinition td in state.AllJavaTypes) {
30+
UpdateApplicationConfig (state, td);
31+
string moduleName = td.Module.Assembly.Name.Name;
32+
ModuleDebugData module;
33+
34+
if (!modules.TryGetValue (moduleName, out module)) {
35+
string outputFileName = $"{moduleName}{TypemapExtension}";
36+
module = new ModuleDebugData {
37+
EntryCount = 0,
38+
JavaNameWidth = 0,
39+
ManagedNameWidth = 0,
40+
JavaToManagedMap = new List<TypeMapDebugEntry> (),
41+
ManagedToJavaMap = new List<TypeMapDebugEntry> (),
42+
OutputFilePath = Path.Combine (typemapFilesOutputDirectory, outputFileName),
43+
ModuleName = moduleName,
44+
ModuleNameBytes = outputEncoding.GetBytes (moduleName),
45+
};
46+
47+
if (module.ModuleNameBytes.Length > maxModuleNameWidth)
48+
maxModuleNameWidth = module.ModuleNameBytes.Length;
49+
50+
if (outputFileName.Length > maxModuleFileNameWidth)
51+
maxModuleFileNameWidth = outputFileName.Length;
52+
53+
modules.Add (moduleName, module);
54+
}
55+
56+
TypeMapDebugEntry entry = GetDebugEntry (td, state.TypeCache);
57+
HandleDebugDuplicates (javaDuplicates, entry, td, state.TypeCache);
58+
if (entry.JavaName.Length > module.JavaNameWidth)
59+
module.JavaNameWidth = (uint) entry.JavaName.Length + 1;
60+
61+
if (entry.ManagedName.Length > module.ManagedNameWidth)
62+
module.ManagedNameWidth = (uint) entry.ManagedName.Length + 1;
63+
64+
module.JavaToManagedMap.Add (entry);
65+
module.ManagedToJavaMap.Add (entry);
66+
}
67+
68+
SyncDebugDuplicates (javaDuplicates);
69+
70+
return modules;
71+
}
72+
73+
public static (List<TypeMapDebugEntry> javaToManaged, List<TypeMapDebugEntry> managedToJava) GetDebugNativeEntries (NativeCodeGenState state)
74+
{
75+
var javaToManaged = new List<TypeMapDebugEntry> ();
76+
var managedToJava = new List<TypeMapDebugEntry> ();
77+
78+
var javaDuplicates = new Dictionary<string, List<TypeMapDebugEntry>> (StringComparer.Ordinal);
79+
foreach (TypeDefinition td in state.AllJavaTypes) {
80+
UpdateApplicationConfig (state, td);
81+
82+
TypeMapDebugEntry entry = GetDebugEntry (td, state.TypeCache);
83+
HandleDebugDuplicates (javaDuplicates, entry, td, state.TypeCache);
84+
85+
javaToManaged.Add (entry);
86+
managedToJava.Add (entry);
87+
}
88+
89+
SyncDebugDuplicates (javaDuplicates);
90+
91+
return (javaToManaged, managedToJava);
92+
}
93+
94+
public static ReleaseGenerationState GetReleaseGenerationState (NativeCodeGenState state)
95+
{
96+
var genState = new ReleaseGenerationState ();
97+
98+
foreach (TypeDefinition td in state.AllJavaTypes) {
99+
ProcessReleaseType (state, genState, td);
100+
}
101+
102+
return genState;
103+
}
104+
105+
106+
static void ProcessReleaseType (NativeCodeGenState state, ReleaseGenerationState genState, TypeDefinition td)
107+
{
108+
UpdateApplicationConfig (state, td);
109+
genState.AddKnownAssembly (GetAssemblyName (td));
110+
111+
// We must NOT use Guid here! The reason is that Guid sort order is different than its corresponding
112+
// byte array representation and on the runtime we need the latter in order to be able to binary search
113+
// through the module array.
114+
byte [] moduleUUID;
115+
if (!genState.MvidCache.TryGetValue (td.Module.Mvid, out moduleUUID)) {
116+
moduleUUID = td.Module.Mvid.ToByteArray ();
117+
genState.MvidCache.Add (td.Module.Mvid, moduleUUID);
118+
}
119+
120+
Dictionary<byte [], ModuleReleaseData> tempModules = genState.TempModules;
121+
if (!tempModules.TryGetValue (moduleUUID, out ModuleReleaseData moduleData)) {
122+
moduleData = new ModuleReleaseData {
123+
Mvid = td.Module.Mvid,
124+
MvidBytes = moduleUUID,
125+
//Assembly = td.Module.Assembly,
126+
AssemblyName = td.Module.Assembly.Name.Name,
127+
TypesScratch = new Dictionary<string, TypeMapReleaseEntry> (StringComparer.Ordinal),
128+
DuplicateTypes = new List<TypeMapReleaseEntry> (),
129+
};
130+
131+
tempModules.Add (moduleUUID, moduleData);
132+
}
133+
134+
string javaName = Java.Interop.Tools.TypeNameMappings.JavaNativeTypeManager.ToJniName (td, state.TypeCache);
135+
// We will ignore generic types and interfaces when generating the Java to Managed map, but we must not
136+
// omit them from the table we output - we need the same number of entries in both java-to-managed and
137+
// managed-to-java tables. `SkipInJavaToManaged` set to `true` will cause the native assembly generator
138+
// to output `0` as the token id for the type, thus effectively causing the runtime unable to match such
139+
// a Java type name to a managed type. This fixes https://github.com/xamarin/xamarin-android/issues/4660
140+
var entry = new TypeMapReleaseEntry {
141+
JavaName = javaName,
142+
ManagedTypeName = td.FullName,
143+
Token = td.MetadataToken.ToUInt32 (),
144+
AssemblyNameIndex = genState.KnownAssemblies [GetAssemblyName (td)],
145+
SkipInJavaToManaged = ShouldSkipInJavaToManaged (td),
146+
};
147+
148+
if (moduleData.TypesScratch.ContainsKey (entry.JavaName)) {
149+
// This is disabled because it costs a lot of time (around 150ms per standard XF Integration app
150+
// build) and has no value for the end user. The message is left here because it may be useful to us
151+
// in our devloop at some point.
152+
//log.LogDebugMessage ($"Warning: duplicate Java type name '{entry.JavaName}' in assembly '{moduleData.AssemblyName}' (new token: {entry.Token}).");
153+
moduleData.DuplicateTypes.Add (entry);
154+
} else {
155+
moduleData.TypesScratch.Add (entry.JavaName, entry);
156+
}
157+
}
158+
159+
static string GetAssemblyName (TypeDefinition td) => td.Module.Assembly.FullName;
160+
161+
static TypeMapDebugEntry GetDebugEntry (TypeDefinition td, TypeDefinitionCache cache)
162+
{
163+
return new TypeMapDebugEntry {
164+
JavaName = Java.Interop.Tools.TypeNameMappings.JavaNativeTypeManager.ToJniName (td, cache),
165+
ManagedName = GetManagedTypeName (td),
166+
TypeDefinition = td,
167+
SkipInJavaToManaged = ShouldSkipInJavaToManaged (td),
168+
};
169+
}
170+
171+
static string GetManagedTypeName (TypeDefinition td)
172+
{
173+
// This is necessary because Mono runtime will return to us type name with a `.` for nested types (not a
174+
// `/` or a `+`. So, for instance, a type named `DefaultRenderer` found in the
175+
// `Xamarin.Forms.Platform.Android.Platform` class in the `Xamarin.Forms.Platform.Android` assembly will
176+
// be seen here as
177+
//
178+
// Xamarin.Forms.Platform.Android.Platform/DefaultRenderer
179+
//
180+
// The managed land name for the type will be rendered as
181+
//
182+
// Xamarin.Forms.Platform.Android.Platform+DefaultRenderer
183+
//
184+
// And this is the form that we need in the map file
185+
//
186+
string managedTypeName = td.FullName.Replace ('/', '+');
187+
188+
return $"{managedTypeName}, {td.Module.Assembly.Name.Name}";
189+
}
190+
191+
192+
static void HandleDebugDuplicates (Dictionary<string, List<TypeMapDebugEntry>> javaDuplicates, TypeMapDebugEntry entry, TypeDefinition td, TypeDefinitionCache cache)
193+
{
194+
List<TypeMapDebugEntry> duplicates;
195+
196+
if (!javaDuplicates.TryGetValue (entry.JavaName, out duplicates)) {
197+
javaDuplicates.Add (entry.JavaName, new List<TypeMapDebugEntry> { entry });
198+
} else {
199+
TypeMapDebugEntry oldEntry = duplicates [0];
200+
if ((td.IsAbstract || td.IsInterface) &&
201+
!oldEntry.TypeDefinition.IsAbstract &&
202+
!oldEntry.TypeDefinition.IsInterface &&
203+
td.IsAssignableFrom (oldEntry.TypeDefinition, cache)) {
204+
// We found the `Invoker` type *before* the declared type
205+
// Fix things up so the abstract type is first, and the `Invoker` is considered a duplicate.
206+
duplicates.Insert (0, entry);
207+
oldEntry.SkipInJavaToManaged = false;
208+
} else {
209+
// ¯\_(ツ)_/¯
210+
duplicates.Add (entry);
211+
}
212+
}
213+
}
214+
215+
static bool ShouldSkipInJavaToManaged (TypeDefinition td)
216+
{
217+
return td.IsInterface || td.HasGenericParameters;
218+
}
219+
220+
static void SyncDebugDuplicates (Dictionary<string, List<TypeMapDebugEntry>> javaDuplicates)
221+
{
222+
foreach (List<TypeMapDebugEntry> duplicates in javaDuplicates.Values) {
223+
if (duplicates.Count < 2) {
224+
continue;
225+
}
226+
227+
// Java duplicates must all point to the same managed type
228+
// Managed types, however, must point back to the original Java type instead
229+
// File/assembly generator use the `DuplicateForJavaToManaged` field to know to which managed type the
230+
// duplicate Java type must be mapped.
231+
TypeMapDebugEntry template = duplicates [0];
232+
for (int i = 1; i < duplicates.Count; i++) {
233+
duplicates [i].DuplicateForJavaToManaged = template;
234+
}
235+
}
236+
}
237+
238+
static void UpdateApplicationConfig (NativeCodeGenState state, TypeDefinition javaType)
239+
{
240+
if (state.JniAddNativeMethodRegistrationAttributePresent || !javaType.HasCustomAttributes) {
241+
return;
242+
}
243+
244+
foreach (CustomAttribute ca in javaType.CustomAttributes) {
245+
if (!state.JniAddNativeMethodRegistrationAttributePresent && String.Compare ("JniAddNativeMethodRegistrationAttribute", ca.AttributeType.Name, StringComparison.Ordinal) == 0) {
246+
state.JniAddNativeMethodRegistrationAttributePresent = true;
247+
break;
248+
}
249+
}
250+
}
251+
}

0 commit comments

Comments
 (0)