Skip to content

Commit b709256

Browse files
Add UCO-specific JniSignatureHelper methods and tests
Add JniParamKind enum, ParseParameterTypes, ParseReturnType, EncodeClrType, ParseSingleType, and their test classes (JniSignatureHelperTests, NegativeEdgeCase) — moved from PR #10808. Also add back JavaConstructors property, JavaConstructorInfo record, and BuildJavaConstructors scanner method needed by ModelBuilder. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent e8d4557 commit b709256

File tree

5 files changed

+189
-0
lines changed

5 files changed

+189
-0
lines changed

src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/JniSignatureHelper.cs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,39 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Reflection.Metadata;
4+
using System.Reflection.Metadata.Ecma335;
35

46
namespace Microsoft.Android.Sdk.TrimmableTypeMap;
57

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+
623
/// <summary>Helpers for parsing JNI method signatures.</summary>
724
static class JniSignatureHelper
825
{
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+
937
/// <summary>Parses the raw JNI type descriptor strings from a JNI method signature.</summary>
1038
public static List<string> ParseParameterTypeStrings (string jniSignature)
1139
{
@@ -26,6 +54,37 @@ public static string ParseReturnTypeString (string jniSignature)
2654
return jniSignature.Substring (i);
2755
}
2856

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+
2988
static void SkipSingleType (string sig, ref int i)
3089
{
3190
switch (sig [i]) {
@@ -44,4 +103,21 @@ static void SkipSingleType (string sig, ref int i)
44103
throw new ArgumentException ($"Unknown JNI type character '{sig [i]}' in '{sig}' at index {i}");
45104
}
46105
}
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.Boolean (); 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+
}
47123
}

src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@ sealed record JavaPeerInfo
6767
/// </summary>
6868
public IReadOnlyList<MarshalMethodInfo> MarshalMethods { get; init; } = Array.Empty<MarshalMethodInfo> ();
6969

70+
/// <summary>
71+
/// Java constructors to emit in the JCW .java file.
72+
/// Each has a JNI signature and an ordinal index for the nctor_N native method.
73+
/// </summary>
74+
public IReadOnlyList<JavaConstructorInfo> JavaConstructors { get; init; } = Array.Empty<JavaConstructorInfo> ();
75+
7076
/// <summary>
7177
/// Information about the activation constructor for this type.
7278
/// May reference a base type's constructor if the type doesn't define its own.
@@ -179,6 +185,34 @@ sealed record JniParameterInfo
179185
public string ManagedType { get; init; } = "";
180186
}
181187

188+
/// <summary>
189+
/// Describes a Java constructor to emit in the JCW .java source file.
190+
/// </summary>
191+
sealed record JavaConstructorInfo
192+
{
193+
/// <summary>
194+
/// JNI constructor signature, e.g., "(Landroid/content/Context;)V".
195+
/// </summary>
196+
public required string JniSignature { get; init; }
197+
198+
/// <summary>
199+
/// Ordinal index for the native constructor method (nctor_0, nctor_1, ...).
200+
/// </summary>
201+
public required int ConstructorIndex { get; init; }
202+
203+
/// <summary>
204+
/// JNI parameter types parsed from the signature.
205+
/// Used to generate the Java constructor parameter list.
206+
/// </summary>
207+
public IReadOnlyList<JniParameterInfo> Parameters { get; init; } = Array.Empty<JniParameterInfo> ();
208+
209+
/// <summary>
210+
/// For [Export] constructors: super constructor arguments string.
211+
/// Null for [Register] constructors.
212+
/// </summary>
213+
public string? SuperArgumentsString { get; init; }
214+
}
215+
182216
/// <summary>
183217
/// Describes how to call the activation constructor for a Java peer type.
184218
/// </summary>

src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ void ScanAssembly (AssemblyIndex index, Dictionary<string, JavaPeerInfo> results
220220
DoNotGenerateAcw = doNotGenerateAcw,
221221
IsUnconditional = isUnconditional,
222222
MarshalMethods = marshalMethods,
223+
JavaConstructors = BuildJavaConstructors (marshalMethods),
223224
ActivationCtor = activationCtor,
224225
InvokerTypeName = invokerTypeName,
225226
IsGenericDefinition = isGenericDefinition,
@@ -708,4 +709,23 @@ static List<JniParameterInfo> ParseJniParameters (string jniSignature)
708709
}
709710
return result;
710711
}
712+
713+
static List<JavaConstructorInfo> BuildJavaConstructors (List<MarshalMethodInfo> marshalMethods)
714+
{
715+
var ctors = new List<JavaConstructorInfo> ();
716+
int ctorIndex = 0;
717+
foreach (var mm in marshalMethods) {
718+
if (!mm.IsConstructor) {
719+
continue;
720+
}
721+
ctors.Add (new JavaConstructorInfo {
722+
JniSignature = mm.JniSignature,
723+
ConstructorIndex = ctorIndex,
724+
Parameters = mm.Parameters,
725+
SuperArgumentsString = mm.SuperArgumentsString,
726+
});
727+
ctorIndex++;
728+
}
729+
return ctors;
730+
}
711731
}

tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/FixtureTestBase.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ protected static JavaPeerInfo MakeAcwPeer (string jniName, string managedName, s
6767
{
6868
var peer = MakePeerWithActivation (jniName, managedName, asmName);
6969
peer.DoNotGenerateAcw = false;
70+
peer.JavaConstructors = new List<JavaConstructorInfo> {
71+
new JavaConstructorInfo { ConstructorIndex = 0, JniSignature = "()V" },
72+
};
7073
peer.MarshalMethods = new List<MarshalMethodInfo> {
7174
new MarshalMethodInfo {
7275
JniName = "<init>",

tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,62 @@ public void Generate_EmptyPeerList_ProducesValidAssembly ()
309309

310310
}
311311

312+
public class JniSignatureHelperTests
313+
{
314+
315+
[Theory]
316+
[InlineData ("()V", 0)]
317+
[InlineData ("(I)V", 1)]
318+
[InlineData ("(Landroid/os/Bundle;)V", 1)]
319+
[InlineData ("(IFJ)V", 3)]
320+
[InlineData ("(ZLandroid/view/View;I)Z", 3)]
321+
[InlineData ("([Ljava/lang/String;)V", 1)]
322+
public void ParseParameterTypes_ParsesCorrectCount (string signature, int expectedCount)
323+
{
324+
var actual = JniSignatureHelper.ParseParameterTypes (signature);
325+
Assert.Equal (expectedCount, actual.Count);
326+
}
327+
328+
[Theory]
329+
[InlineData ("(Z)V", JniParamKind.Boolean)]
330+
[InlineData ("(Ljava/lang/String;)V", JniParamKind.Object)]
331+
public void ParseParameterTypes_SingleParam_MapsToCorrectKind (string signature, JniParamKind expectedKind)
332+
{
333+
var types = JniSignatureHelper.ParseParameterTypes (signature);
334+
Assert.Single (types);
335+
Assert.Equal (expectedKind, types [0]);
336+
}
337+
338+
[Theory]
339+
[InlineData ("()V", JniParamKind.Void)]
340+
[InlineData ("()I", JniParamKind.Int)]
341+
[InlineData ("()Z", JniParamKind.Boolean)]
342+
[InlineData ("()Ljava/lang/String;", JniParamKind.Object)]
343+
public void ParseReturnType_MapsToCorrectKind (string signature, JniParamKind expectedKind)
344+
{
345+
Assert.Equal (expectedKind, JniSignatureHelper.ParseReturnType (signature));
346+
}
347+
348+
}
349+
350+
public class NegativeEdgeCase
351+
{
352+
353+
[Theory]
354+
[InlineData ("")]
355+
[InlineData ("not-a-sig")]
356+
[InlineData ("(")]
357+
public void ParseParameterTypes_InvalidSignature_ThrowsOrReturnsEmpty (string signature)
358+
{
359+
try {
360+
var result = JniSignatureHelper.ParseParameterTypes (signature);
361+
Assert.NotNull (result);
362+
} catch (Exception ex) when (ex is ArgumentException || ex is IndexOutOfRangeException || ex is FormatException) {
363+
}
364+
}
365+
366+
}
367+
312368
public class CreateInstancePaths
313369
{
314370

0 commit comments

Comments
 (0)