Skip to content

Commit 0ecbfd8

Browse files
[TrimmableTypeMap] Add TypeMap proxy groundwork with CreateInstance
Add the core pipeline that transforms scanned JavaPeerInfo records into TypeMap .dll assemblies with proxy types and CreateInstance support: - TypeMapAssemblyData model: proxy types, TypeMap attributes, associations - ModelBuilder: transforms JavaPeerInfo into model with alias detection, ACW filtering, proxy naming, cross-assembly IgnoresAccessChecksTo - TypeMapAssemblyEmitter: PE/IL emission of proxy types with CreateInstance, get_TargetType, get_InvokerType properties - TypeMapAssemblyGenerator: orchestrates Build + Emit pipeline - RootTypeMapAssemblyGenerator: generates root _Microsoft.Android.TypeMaps.dll - JniSignatureHelper: JNI signature parsing utility - Scanner enrichment for generator consumption - Comprehensive tests for model builder, assembly generator, root generator UCO wrappers, RegisterNatives, and IAndroidCallableWrapper support will be added in a follow-up PR. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 03926b5 commit 0ecbfd8

File tree

11 files changed

+3314
-0
lines changed

11 files changed

+3314
-0
lines changed
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Reflection.Metadata;
4+
using System.Reflection.Metadata.Ecma335;
5+
6+
namespace Microsoft.Android.Sdk.TrimmableTypeMap;
7+
8+
/// <summary>JNI primitive type kinds used for mapping JNI signatures → CLR types.</summary>
9+
enum JniParamKind
10+
{
11+
Void, // V
12+
Boolean, // Z → sbyte
13+
Byte, // B → sbyte
14+
Char, // C → char
15+
Short, // S → short
16+
Int, // I → int
17+
Long, // J → long
18+
Float, // F → float
19+
Double, // D → double
20+
Object, // L...; or [ → IntPtr
21+
}
22+
23+
/// <summary>Helpers for parsing JNI method signatures.</summary>
24+
static class JniSignatureHelper
25+
{
26+
/// <summary>Parses the parameter types from a JNI method signature like "(Landroid/os/Bundle;)V".</summary>
27+
public static List<JniParamKind> ParseParameterTypes (string jniSignature)
28+
{
29+
var result = new List<JniParamKind> ();
30+
int i = 1; // skip opening '('
31+
while (i < jniSignature.Length && jniSignature [i] != ')') {
32+
result.Add (ParseSingleType (jniSignature, ref i));
33+
}
34+
return result;
35+
}
36+
37+
/// <summary>Parses the raw JNI type descriptor strings from a JNI method signature.</summary>
38+
public static List<string> ParseParameterTypeStrings (string jniSignature)
39+
{
40+
var result = new List<string> ();
41+
int i = 1; // skip opening '('
42+
while (i < jniSignature.Length && jniSignature [i] != ')') {
43+
int start = i;
44+
SkipSingleType (jniSignature, ref i);
45+
result.Add (jniSignature.Substring (start, i - start));
46+
}
47+
return result;
48+
}
49+
50+
/// <summary>Extracts the return type descriptor from a JNI method signature.</summary>
51+
public static string ParseReturnTypeString (string jniSignature)
52+
{
53+
int i = jniSignature.IndexOf (')') + 1;
54+
return jniSignature.Substring (i);
55+
}
56+
57+
/// <summary>Parses the return type from a JNI method signature.</summary>
58+
public static JniParamKind ParseReturnType (string jniSignature)
59+
{
60+
int i = jniSignature.IndexOf (')') + 1;
61+
return ParseSingleType (jniSignature, ref i);
62+
}
63+
64+
static JniParamKind ParseSingleType (string sig, ref int i)
65+
{
66+
switch (sig [i]) {
67+
case 'V': i++; return JniParamKind.Void;
68+
case 'Z': i++; return JniParamKind.Boolean;
69+
case 'B': i++; return JniParamKind.Byte;
70+
case 'C': i++; return JniParamKind.Char;
71+
case 'S': i++; return JniParamKind.Short;
72+
case 'I': i++; return JniParamKind.Int;
73+
case 'J': i++; return JniParamKind.Long;
74+
case 'F': i++; return JniParamKind.Float;
75+
case 'D': i++; return JniParamKind.Double;
76+
case 'L':
77+
i = sig.IndexOf (';', i) + 1;
78+
return JniParamKind.Object;
79+
case '[':
80+
i++;
81+
ParseSingleType (sig, ref i); // skip element type
82+
return JniParamKind.Object;
83+
default:
84+
throw new ArgumentException ($"Unknown JNI type character '{sig [i]}' in '{sig}' at index {i}");
85+
}
86+
}
87+
88+
static void SkipSingleType (string sig, ref int i)
89+
{
90+
switch (sig [i]) {
91+
case 'V': case 'Z': case 'B': case 'C': case 'S':
92+
case 'I': case 'J': case 'F': case 'D':
93+
i++;
94+
break;
95+
case 'L':
96+
i = sig.IndexOf (';', i) + 1;
97+
break;
98+
case '[':
99+
i++;
100+
SkipSingleType (sig, ref i);
101+
break;
102+
default:
103+
throw new ArgumentException ($"Unknown JNI type character '{sig [i]}' in '{sig}' at index {i}");
104+
}
105+
}
106+
107+
/// <summary>Encodes the CLR type for a JNI parameter kind into a signature type encoder.</summary>
108+
public static void EncodeClrType (SignatureTypeEncoder encoder, JniParamKind kind)
109+
{
110+
switch (kind) {
111+
case JniParamKind.Boolean: encoder.SByte (); break;
112+
case JniParamKind.Byte: encoder.SByte (); break;
113+
case JniParamKind.Char: encoder.Char (); break;
114+
case JniParamKind.Short: encoder.Int16 (); break;
115+
case JniParamKind.Int: encoder.Int32 (); break;
116+
case JniParamKind.Long: encoder.Int64 (); break;
117+
case JniParamKind.Float: encoder.Single (); break;
118+
case JniParamKind.Double: encoder.Double (); break;
119+
case JniParamKind.Object: encoder.IntPtr (); break;
120+
default: throw new ArgumentException ($"Cannot encode JNI param kind {kind} as CLR type");
121+
}
122+
}
123+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace Microsoft.Android.Sdk.TrimmableTypeMap;
5+
6+
/// <summary>
7+
/// Data model for a single TypeMap output assembly.
8+
/// Describes what to emit — the emitter writes this directly into a PE assembly.
9+
/// Built by <see cref="ModelBuilder"/>, consumed by <see cref="TypeMapAssemblyGenerator"/>.
10+
/// </summary>
11+
sealed class TypeMapAssemblyData
12+
{
13+
/// <summary>Assembly name (e.g., "_MyApp.TypeMap").</summary>
14+
public required string AssemblyName { get; init; }
15+
16+
/// <summary>Module file name (e.g., "_MyApp.TypeMap.dll").</summary>
17+
public required string ModuleName { get; init; }
18+
19+
/// <summary>TypeMap entries — one per unique JNI name.</summary>
20+
public List<TypeMapAttributeData> Entries { get; } = new ();
21+
22+
/// <summary>Proxy types to emit in the assembly.</summary>
23+
public List<JavaPeerProxyData> ProxyTypes { get; } = new ();
24+
25+
/// <summary>TypeMapAssociation entries for alias groups (multiple managed types → same JNI name).</summary>
26+
public List<TypeMapAssociationData> Associations { get; } = new ();
27+
28+
/// <summary>Assembly names that need [IgnoresAccessChecksTo] for cross-assembly n_* calls.</summary>
29+
public List<string> IgnoresAccessChecksTo { get; } = new ();
30+
}
31+
32+
/// <summary>
33+
/// One [assembly: TypeMap("jni/name", typeof(Proxy))] or
34+
/// [assembly: TypeMap("jni/name", typeof(Proxy), typeof(Target))] entry.
35+
///
36+
/// 2-arg (unconditional): proxy is always preserved — used for ACW types and essential runtime types.
37+
/// 3-arg (trimmable): proxy is preserved only if Target type is referenced by the app.
38+
/// </summary>
39+
sealed record TypeMapAttributeData
40+
{
41+
/// <summary>JNI type name, e.g., "android/app/Activity".</summary>
42+
public required string JniName { get; init; }
43+
44+
/// <summary>
45+
/// Assembly-qualified proxy type reference string.
46+
/// Either points to a generated proxy or to the original managed type.
47+
/// </summary>
48+
public required string ProxyTypeReference { get; init; }
49+
50+
/// <summary>
51+
/// Assembly-qualified target type reference for the trimmable (3-arg) variant.
52+
/// Null for unconditional (2-arg) entries.
53+
/// The trimmer preserves the proxy only if this target type is used by the app.
54+
/// </summary>
55+
public string? TargetTypeReference { get; init; }
56+
57+
/// <summary>True for 2-arg unconditional entries (ACW types, essential runtime types).</summary>
58+
public bool IsUnconditional => TargetTypeReference == null;
59+
}
60+
61+
/// <summary>
62+
/// A proxy type to generate in the TypeMap assembly (subclass of JavaPeerProxy).
63+
/// </summary>
64+
sealed class JavaPeerProxyData
65+
{
66+
/// <summary>Simple type name, e.g., "Java_Lang_Object_Proxy".</summary>
67+
public required string TypeName { get; init; }
68+
69+
/// <summary>Namespace for all proxy types.</summary>
70+
public string Namespace { get; init; } = "_TypeMap.Proxies";
71+
72+
/// <summary>Reference to the managed type this proxy wraps (for ldtoken in TargetType property).</summary>
73+
public required TypeRefData TargetType { get; init; }
74+
75+
/// <summary>Reference to the invoker type (for interfaces/abstract types). Null if not applicable.</summary>
76+
public TypeRefData? InvokerType { get; set; }
77+
78+
/// <summary>Whether this proxy has a CreateInstance that can actually create instances.</summary>
79+
public bool HasActivation => ActivationCtor != null || InvokerType != null;
80+
81+
/// <summary>
82+
/// Activation constructor details. Determines how CreateInstance instantiates the managed peer.
83+
/// </summary>
84+
public ActivationCtorData? ActivationCtor { get; set; }
85+
86+
/// <summary>True if this is an open generic type definition. CreateInstance throws NotSupportedException.</summary>
87+
public bool IsGenericDefinition { get; init; }
88+
89+
}
90+
91+
92+
/// <summary>
93+
/// A cross-assembly type reference (assembly name + full managed type name).
94+
/// </summary>
95+
sealed record TypeRefData
96+
{
97+
/// <summary>Full managed type name, e.g., "Android.App.Activity" or "MyApp.Outer+Inner".</summary>
98+
public required string ManagedTypeName { get; init; }
99+
100+
/// <summary>Assembly containing the type, e.g., "Mono.Android".</summary>
101+
public required string AssemblyName { get; init; }
102+
}
103+
104+
/// <summary>
105+
/// Describes how the proxy's CreateInstance should construct the managed peer.
106+
/// </summary>
107+
sealed record ActivationCtorData
108+
{
109+
/// <summary>Type that declares the activation constructor (may be a base type).</summary>
110+
public required TypeRefData DeclaringType { get; init; }
111+
112+
/// <summary>True when the leaf type itself declares the activation ctor.</summary>
113+
public required bool IsOnLeafType { get; init; }
114+
115+
/// <summary>The style of activation ctor (XamarinAndroid or JavaInterop).</summary>
116+
public required ActivationCtorStyle Style { get; init; }
117+
}
118+
119+
/// <summary>
120+
/// One [assembly: TypeMapAssociation(typeof(Source), typeof(AliasProxy))] entry.
121+
/// Links a managed type to the proxy that holds its alias TypeMap entry.
122+
/// </summary>
123+
sealed record TypeMapAssociationData
124+
{
125+
/// <summary>Assembly-qualified source type reference (the managed alias type).</summary>
126+
public required string SourceTypeReference { get; init; }
127+
128+
/// <summary>Assembly-qualified proxy type reference (the alias holder proxy).</summary>
129+
public required string AliasProxyTypeReference { get; init; }
130+
}

0 commit comments

Comments
 (0)