Skip to content

Commit 81dc9a2

Browse files
committed
[Bytecode] Surface Kotlin's unsigned types in .class-parse XML
1 parent f3553f4 commit 81dc9a2

File tree

10 files changed

+190
-20
lines changed

10 files changed

+190
-20
lines changed

src/Xamarin.Android.Tools.Bytecode/Fields.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public sealed class FieldInfo {
3333
public ConstantPool ConstantPool {get; private set;}
3434
public FieldAccessFlags AccessFlags {get; private set;}
3535
public AttributeCollection Attributes {get; private set;}
36+
public string KotlinType { get; set; }
3637

3738
public FieldInfo (ConstantPool constantPool, Stream stream)
3839
{

src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinClassMetadata.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Diagnostics;
34
using System.Linq;
45
using System.Text;
56
using org.jetbrains.kotlin.metadata.jvm;
7+
using ProtoBuf;
68
using Type = org.jetbrains.kotlin.metadata.jvm.Type;
79

810
namespace Xamarin.Android.Tools.Bytecode
911
{
12+
// https://github.com/JetBrains/kotlin/blob/master/core/metadata.jvm/src/jvm_metadata.proto
1013
public class KotlinFile
1114
{
1215
public List<KotlinFunction> Functions { get; set; }
@@ -220,6 +223,8 @@ internal static KotlinExpression FromProtobuf (Expression exp, JvmNameResolver r
220223
public class KotlinFunction : KotlinMethodBase
221224
{
222225
public string Name { get; set; }
226+
public string JvmName { get; set; }
227+
public string JvmSignature { get; set; }
223228
public KotlinFunctionFlags Flags { get; set; }
224229
public KotlinType ReturnType { get; set; }
225230
public int ReturnTypeId { get; set; }
@@ -235,9 +240,13 @@ internal static KotlinFunction FromProtobuf (Function f, JvmNameResolver resolve
235240
if (f is null)
236241
return null;
237242

243+
var sig = Extensible.GetValue<JvmMethodSignature> (f, 100);
244+
238245
return new KotlinFunction {
239246
Flags = (KotlinFunctionFlags)f.Flags,
240247
Name = resolver.GetString (f.Name),
248+
JvmName = resolver.GetString ((sig?.Name ?? 0) > 0 ? sig.Name : f.Name),
249+
JvmSignature = sig is null ? null : resolver.GetString (sig.Desc),
241250
ReturnType = KotlinType.FromProtobuf (f.ReturnType, resolver),
242251
ReturnTypeId = f.ReturnTypeId,
243252
ReceiverType = KotlinType.FromProtobuf (f.ReceiverType, resolver),

src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinFixups.cs

Lines changed: 63 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,15 @@ public static void Fixup (IList<ClassFile> classes)
4545
FixupJavaMethods (c.Methods);
4646

4747
foreach (var met in metadata.Functions)
48-
FixupFunction (FindJavaMethod (class_metadata, met, c), met, class_metadata);
48+
FixupFunction (FindJavaMethod (metadata, met, c), met, class_metadata);
4949

5050
foreach (var prop in metadata.Properties) {
51-
var getter = FindJavaPropertyGetter (class_metadata, prop, c);
52-
var setter = FindJavaPropertySetter (class_metadata, prop, c);
51+
var getter = FindJavaPropertyGetter (metadata, prop, c);
52+
var setter = FindJavaPropertySetter (metadata, prop, c);
5353

5454
FixupProperty (getter, setter, prop);
55+
56+
FixupField (FindJavaFieldProperty (metadata, prop, c), prop);
5557
}
5658

5759
} catch (Exception ex) {
@@ -96,7 +98,6 @@ static void FixupConstructor (MethodInfo method, KotlinConstructor metadata)
9698
Log.Debug ($"Kotlin: Hiding internal constructor {method.DeclaringType?.ThisClass.Name.Value} - {metadata.GetSignature ()}");
9799
method.AccessFlags = MethodAccessFlags.Private;
98100
}
99-
100101
}
101102

102103
static void FixupFunction (MethodInfo method, KotlinFunction metadata, KotlinClass kotlinClass)
@@ -111,18 +112,24 @@ static void FixupFunction (MethodInfo method, KotlinFunction metadata, KotlinCla
111112
return;
112113
}
113114

114-
// Kotlin provides actual parameter names
115115
var java_parameters = method.GetFilteredParameters ();
116116

117117
for (var i = 0; i < java_parameters.Length; i++) {
118118
var java_p = java_parameters [i];
119119
var kotlin_p = metadata.ValueParameters [i];
120120

121+
// Kotlin provides actual parameter names
121122
if (TypesMatch (java_p.Type, kotlin_p.Type, kotlinClass) && java_p.IsUnnamedParameter () && !kotlin_p.IsUnnamedParameter ()) {
122123
Log.Debug ($"Kotlin: Renaming parameter {method.DeclaringType?.ThisClass.Name.Value} - {method.Name} - {java_p.Name} -> {kotlin_p.Name}");
123124
java_p.Name = kotlin_p.Name;
124125
}
126+
127+
// Handle erasure of Kotlin unsigned types
128+
java_p.KotlinType = GetKotlinType (java_p.Type.TypeSignature, kotlin_p.Type.ClassName);
125129
}
130+
131+
// Handle erasure of Kotlin unsigned types
132+
method.KotlinReturnType = GetKotlinType (method.ReturnType.TypeSignature, metadata.ReturnType.ClassName);
126133
}
127134

128135
static void FixupExtensionMethod (MethodInfo method)
@@ -158,16 +165,32 @@ static void FixupProperty (MethodInfo getter, MethodInfo setter, KotlinProperty
158165
return;
159166
}
160167

168+
// Handle erasure of Kotlin unsigned types
169+
if (getter != null)
170+
getter.KotlinReturnType = GetKotlinType (getter.ReturnType.TypeSignature, metadata.ReturnType.ClassName);
171+
161172
if (setter != null) {
162173
var setter_parameter = setter.GetParameters ().First ();
163174

164-
if (setter_parameter.IsUnnamedParameter ()) {
175+
if (setter_parameter.IsUnnamedParameter () || setter_parameter.Name == "<set-?>") {
165176
Log.Debug ($"Kotlin: Renaming setter parameter {setter.DeclaringType?.ThisClass.Name.Value} - {setter.Name} - {setter_parameter.Name} -> value");
166177
setter_parameter.Name = "value";
167178
}
179+
180+
// Handle erasure of Kotlin unsigned types
181+
setter_parameter.KotlinType = GetKotlinType (setter_parameter.Type.TypeSignature, metadata.ReturnType.ClassName);
168182
}
169183
}
170184

185+
static void FixupField (FieldInfo field, KotlinProperty metadata)
186+
{
187+
if (field is null)
188+
return;
189+
190+
// Handle erasure of Kotlin unsigned types
191+
field.KotlinType = GetKotlinType (field.Descriptor, metadata.ReturnType.ClassName);
192+
}
193+
171194
static MethodInfo FindJavaConstructor (KotlinClass kotlinClass, KotlinConstructor constructor, ClassFile klass)
172195
{
173196
var all_constructors = klass.Methods.Where (method => method.Name == "<init>" || method.Name == "<clinit>");
@@ -181,16 +204,16 @@ static MethodInfo FindJavaConstructor (KotlinClass kotlinClass, KotlinConstructo
181204
return null;
182205
}
183206

184-
static MethodInfo FindJavaMethod (KotlinClass kotlinClass, KotlinFunction function, ClassFile klass)
207+
static MethodInfo FindJavaMethod (KotlinFile kotlinFile, KotlinFunction function, ClassFile klass)
185208
{
186-
var possible_methods = klass.Methods.Where (method => method.GetMethodNameWithoutSuffix () == function.Name &&
209+
var possible_methods = klass.Methods.Where (method => method.Name == function.JvmName &&
187210
method.GetFilteredParameters ().Length == function.ValueParameters.Count);
188211

189212
foreach (var method in possible_methods) {
190-
if (!TypesMatch (method.ReturnType, function.ReturnType, kotlinClass))
213+
if (!TypesMatch (method.ReturnType, function.ReturnType, kotlinFile))
191214
continue;
192215

193-
if (!ParametersMatch (kotlinClass, method, function.ValueParameters))
216+
if (!ParametersMatch (kotlinFile, method, function.ValueParameters))
194217
continue;
195218

196219
return method;
@@ -199,7 +222,15 @@ static MethodInfo FindJavaMethod (KotlinClass kotlinClass, KotlinFunction functi
199222
return null;
200223
}
201224

202-
static MethodInfo FindJavaPropertyGetter (KotlinClass kotlinClass, KotlinProperty property, ClassFile klass)
225+
static FieldInfo FindJavaFieldProperty (KotlinFile kotlinClass, KotlinProperty property, ClassFile klass)
226+
{
227+
var possible_methods = klass.Fields.Where (field => field.Name == property.Name &&
228+
TypesMatch (new TypeInfo (field.Descriptor, field.Descriptor), property.ReturnType, kotlinClass));
229+
230+
return possible_methods.FirstOrDefault ();
231+
}
232+
233+
static MethodInfo FindJavaPropertyGetter (KotlinFile kotlinClass, KotlinProperty property, ClassFile klass)
203234
{
204235
var possible_methods = klass.Methods.Where (method => (string.Compare (method.GetMethodNameWithoutSuffix (), $"get{property.Name}", true) == 0 ||
205236
string.Compare (method.GetMethodNameWithoutSuffix (), property.Name, true) == 0) &&
@@ -209,7 +240,7 @@ static MethodInfo FindJavaPropertyGetter (KotlinClass kotlinClass, KotlinPropert
209240
return possible_methods.FirstOrDefault ();
210241
}
211242

212-
static MethodInfo FindJavaPropertySetter (KotlinClass kotlinClass, KotlinProperty property, ClassFile klass)
243+
static MethodInfo FindJavaPropertySetter (KotlinFile kotlinClass, KotlinProperty property, ClassFile klass)
213244
{
214245
var possible_methods = klass.Methods.Where (method => string.Compare (method.GetMethodNameWithoutSuffix (), $"set{property.Name}", true) == 0 &&
215246
property.ReturnType != null &&
@@ -219,7 +250,7 @@ static MethodInfo FindJavaPropertySetter (KotlinClass kotlinClass, KotlinPropert
219250
return possible_methods.FirstOrDefault ();
220251
}
221252

222-
static bool ParametersMatch (KotlinClass kotlinClass, MethodInfo method, List<KotlinValueParameter> kotlinParameters)
253+
static bool ParametersMatch (KotlinFile kotlinClass, MethodInfo method, List<KotlinValueParameter> kotlinParameters)
223254
{
224255
var java_parameters = method.GetFilteredParameters ();
225256

@@ -237,13 +268,13 @@ static bool ParametersMatch (KotlinClass kotlinClass, MethodInfo method, List<Ko
237268
return true;
238269
}
239270

240-
static bool TypesMatch (TypeInfo javaType, KotlinType kotlinType, KotlinClass kotlinClass)
271+
static bool TypesMatch (TypeInfo javaType, KotlinType kotlinType, KotlinFile kotlinFile)
241272
{
242273
// Generic type
243274
if (!string.IsNullOrWhiteSpace (kotlinType.TypeParameterName) && $"T{kotlinType.TypeParameterName};" == javaType.TypeSignature)
244275
return true;
245276

246-
if (javaType.BinaryName == KotlinUtilities.ConvertKotlinTypeSignature (kotlinType, kotlinClass))
277+
if (javaType.BinaryName == KotlinUtilities.ConvertKotlinTypeSignature (kotlinType, kotlinFile))
247278
return true;
248279

249280
// Could be a generic type erasure
@@ -253,17 +284,32 @@ static bool TypesMatch (TypeInfo javaType, KotlinType kotlinType, KotlinClass ko
253284
// Sometimes Kotlin keeps its native types rather than converting them to Java native types
254285
// ie: "Lkotlin/UShort;" instead of "S"
255286
if (javaType.BinaryName.StartsWith ("L", StringComparison.Ordinal) && javaType.BinaryName.EndsWith (";", StringComparison.Ordinal)) {
256-
if (KotlinUtilities.ConvertKotlinClassToJava (javaType.BinaryName.Substring (1, javaType.BinaryName.Length - 2)) == KotlinUtilities.ConvertKotlinTypeSignature (kotlinType, kotlinClass))
287+
if (KotlinUtilities.ConvertKotlinClassToJava (javaType.BinaryName.Substring (1, javaType.BinaryName.Length - 2)) == KotlinUtilities.ConvertKotlinTypeSignature (kotlinType, kotlinFile))
257288
return true;
258289
}
259290

260291
// Same for some arrays
261292
if (javaType.BinaryName.StartsWith ("[L", StringComparison.Ordinal) && javaType.BinaryName.EndsWith (";", StringComparison.Ordinal)) {
262-
if ("[" + KotlinUtilities.ConvertKotlinClassToJava (javaType.BinaryName.Substring (2, javaType.BinaryName.Length - 3)) == KotlinUtilities.ConvertKotlinTypeSignature (kotlinType, kotlinClass))
293+
if ("[" + KotlinUtilities.ConvertKotlinClassToJava (javaType.BinaryName.Substring (2, javaType.BinaryName.Length - 3)) == KotlinUtilities.ConvertKotlinTypeSignature (kotlinType, kotlinFile))
263294
return true;
264295
}
265296

266297
return false;
267298
}
299+
300+
static string GetKotlinType (string jvmType, string kotlinClass)
301+
{
302+
// Handle erasure of Kotlin unsigned types
303+
if (jvmType == "I" && kotlinClass == "kotlin/UInt;")
304+
return "uint";
305+
if (jvmType == "S" && kotlinClass == "kotlin/UShort;")
306+
return "ushort";
307+
if (jvmType == "J" && kotlinClass == "kotlin/ULong;")
308+
return "ulong";
309+
if (jvmType == "B" && kotlinClass == "kotlin/UByte;")
310+
return "ubyte";
311+
312+
return null;
313+
}
268314
}
269315
}

src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinUtilities.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ namespace Xamarin.Android.Tools.Bytecode
88
{
99
public static class KotlinUtilities
1010
{
11-
public static string ConvertKotlinTypeSignature (KotlinType type, KotlinClass klass = null)
11+
public static string ConvertKotlinTypeSignature (KotlinType type, KotlinFile metadata = null)
1212
{
1313
if (type is null)
1414
return string.Empty;
1515

1616
var class_name = type.ClassName;
1717

1818
if (string.IsNullOrWhiteSpace (class_name)) {
19-
if (klass is object) {
19+
if (metadata is KotlinClass klass) {
2020

2121
var tp = klass.TypeParameters.FirstOrDefault (t => t.Id == type.TypeParameter);
2222

src/Xamarin.Android.Tools.Bytecode/Methods.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public sealed class MethodInfo {
3131
public ClassFile DeclaringType {get; private set;}
3232
public MethodAccessFlags AccessFlags {get; set;}
3333
public AttributeCollection Attributes {get; private set;}
34+
public string KotlinReturnType {get; set;}
3435

3536
public MethodInfo (ConstantPool constantPool, ClassFile declaringType, Stream stream)
3637
{
@@ -290,6 +291,7 @@ public sealed class ParameterInfo : IEquatable<ParameterInfo> {
290291
public string Name;
291292
public int Position;
292293
public TypeInfo Type = new TypeInfo ();
294+
public string KotlinType;
293295

294296
public MethodParameterAccessFlags AccessFlags;
295297

src/Xamarin.Android.Tools.Bytecode/XmlClassDeclarationBuilder.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,8 @@ XElement GetMethod (string element, string name, MethodInfo method, string retur
332332
var ret = returns != null
333333
? new XAttribute ("return", SignatureToGenericJavaTypeName (returns))
334334
: null;
335+
if (!string.IsNullOrWhiteSpace (method.KotlinReturnType))
336+
ret?.SetValue (method.KotlinReturnType);
335337
var jniRet = returns != null
336338
? new XAttribute ("jni-return", returns)
337339
: null;
@@ -400,6 +402,8 @@ IEnumerable<XElement> GetMethodParameters (MethodInfo method)
400402
genericType = genericType.Substring (1);
401403
}
402404
genericType = SignatureToGenericJavaTypeName (genericType);
405+
if (!string.IsNullOrWhiteSpace (p.KotlinType))
406+
genericType = p.KotlinType;
403407
if (varargArray) {
404408
type += "...";
405409
genericType += "...";
@@ -480,14 +484,17 @@ IEnumerable<XElement> GetFields ()
480484
var visibility = GetVisibility (field.AccessFlags);
481485
if (visibility == "private" || visibility == "")
482486
continue;
487+
var type = new XAttribute ("type", SignatureToJavaTypeName (field.Descriptor));
488+
if (!string.IsNullOrWhiteSpace (field.KotlinType))
489+
type.SetValue (field.KotlinType);
483490
yield return new XElement ("field",
484491
new XAttribute ("deprecated", GetDeprecatedValue (field.Attributes)),
485492
new XAttribute ("final", (field.AccessFlags & FieldAccessFlags.Final) != 0),
486493
new XAttribute ("name", field.Name),
487494
new XAttribute ("static", (field.AccessFlags & FieldAccessFlags.Static) != 0),
488495
new XAttribute ("synthetic", (field.AccessFlags & FieldAccessFlags.Synthetic) != 0),
489496
new XAttribute ("transient", (field.AccessFlags & FieldAccessFlags.Transient) != 0),
490-
new XAttribute ("type", SignatureToJavaTypeName (field.Descriptor)),
497+
type,
491498
new XAttribute ("type-generic-aware", GetGenericType (field)),
492499
new XAttribute ("jni-signature", field.Descriptor),
493500
GetNotNull (field),

tests/Xamarin.Android.Tools.Bytecode-Tests/KotlinFixupsTests.cs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,5 +119,87 @@ public void RenameSetterParameter ()
119119

120120
Assert.AreEqual ("value", p.Name);
121121
}
122+
123+
[Test]
124+
public void UnsignedMethods ()
125+
{
126+
var klass = LoadClassFile ("UnsignedTypes.class");
127+
128+
var uint_method = klass.Methods.First (m => m.Name.Contains ('-') && m.GetParameters () [0].Type.BinaryName == "I");
129+
var ushort_method = klass.Methods.First (m => m.Name.Contains ('-') && m.GetParameters () [0].Type.BinaryName == "S");
130+
var ulong_method = klass.Methods.First (m => m.Name.Contains ('-') && m.GetParameters () [0].Type.BinaryName == "J");
131+
var ubyte_method = klass.Methods.First (m => m.Name.Contains ('-') && m.GetParameters () [0].Type.BinaryName == "B");
132+
133+
KotlinFixups.Fixup (new [] { klass });
134+
135+
Assert.AreEqual ("uint", uint_method.GetParameters () [0].KotlinType);
136+
Assert.AreEqual ("uint", uint_method.KotlinReturnType);
137+
138+
Assert.AreEqual ("ushort", ushort_method.GetParameters () [0].KotlinType);
139+
Assert.AreEqual ("ushort", ushort_method.KotlinReturnType);
140+
141+
Assert.AreEqual ("ulong", ulong_method.GetParameters () [0].KotlinType);
142+
Assert.AreEqual ("ulong", ulong_method.KotlinReturnType);
143+
144+
Assert.AreEqual ("ubyte", ubyte_method.GetParameters () [0].KotlinType);
145+
Assert.AreEqual ("ubyte", ubyte_method.KotlinReturnType);
146+
}
147+
148+
[Test]
149+
public void UnsignedFields ()
150+
{
151+
var klass = LoadClassFile ("UnsignedTypesKt.class");
152+
153+
var uint_field = klass.Fields.Single (m => m.Name == "UINT_CONST");
154+
var ushort_field = klass.Fields.Single (m => m.Name == "USHORT_CONST");
155+
var ulong_field = klass.Fields.Single (m => m.Name == "ULONG_CONST");
156+
var ubyte_field = klass.Fields.Single (m => m.Name == "UBYTE_CONST");
157+
158+
KotlinFixups.Fixup (new [] { klass });
159+
160+
Assert.AreEqual ("uint", uint_field.KotlinType);
161+
Assert.AreEqual ("ushort", ushort_field.KotlinType);
162+
Assert.AreEqual ("ulong", ulong_field.KotlinType);
163+
Assert.AreEqual ("ubyte", ubyte_field.KotlinType);
164+
}
165+
166+
[Test]
167+
public void UnsignedFieldsXml ()
168+
{
169+
// Ensure Kotlin unsigned types end up in the xml
170+
var klass = LoadClassFile ("UnsignedTypesKt.class");
171+
172+
KotlinFixups.Fixup (new [] { klass });
173+
174+
var xml = new XmlClassDeclarationBuilder (klass).ToXElement ();
175+
176+
Assert.AreEqual ("uint", xml.Elements ("field").Single (f => f.Attribute ("name").Value == "UINT_CONST").Attribute ("type").Value);
177+
Assert.AreEqual ("ushort", xml.Elements ("field").Single (f => f.Attribute ("name").Value == "USHORT_CONST").Attribute ("type").Value);
178+
Assert.AreEqual ("ulong", xml.Elements ("field").Single (f => f.Attribute ("name").Value == "ULONG_CONST").Attribute ("type").Value);
179+
Assert.AreEqual ("ubyte", xml.Elements ("field").Single (f => f.Attribute ("name").Value == "UBYTE_CONST").Attribute ("type").Value);
180+
}
181+
182+
[Test]
183+
public void UnsignedMethodsXml ()
184+
{
185+
// Ensure Kotlin unsigned types end up in the xml
186+
var klass = LoadClassFile ("UnsignedTypes.class");
187+
188+
KotlinFixups.Fixup (new [] { klass });
189+
190+
var xml = new XmlClassDeclarationBuilder (klass).ToXElement ();
191+
192+
Assert.AreEqual ("uint", xml.Elements ("method").Single (f => f.Attribute ("name").Value == "foo_uint-WZ4Q5Ns").Attribute ("return").Value);
193+
Assert.AreEqual ("uint", xml.Elements ("method").Single (f => f.Attribute ("name").Value == "foo_uint-WZ4Q5Ns").Element ("parameter").Attribute ("type").Value);
194+
195+
Assert.AreEqual ("ushort", xml.Elements ("method").Single (f => f.Attribute ("name").Value == "foo_ushort-xj2QHRw").Attribute ("return").Value);
196+
Assert.AreEqual ("ushort", xml.Elements ("method").Single (f => f.Attribute ("name").Value == "foo_ushort-xj2QHRw").Element ("parameter").Attribute ("type").Value);
197+
198+
Assert.AreEqual ("ulong", xml.Elements ("method").Single (f => f.Attribute ("name").Value == "foo_ulong-VKZWuLQ").Attribute ("return").Value);
199+
Assert.AreEqual ("ulong", xml.Elements ("method").Single (f => f.Attribute ("name").Value == "foo_ulong-VKZWuLQ").Element ("parameter").Attribute ("type").Value);
200+
201+
Assert.AreEqual ("ubyte", xml.Elements ("method").Single (f => f.Attribute ("name").Value == "foo_ubyte-7apg3OU").Attribute ("return").Value);
202+
Assert.AreEqual ("ubyte", xml.Elements ("method").Single (f => f.Attribute ("name").Value == "foo_ubyte-7apg3OU").Element ("parameter").Attribute ("type").Value);
203+
}
122204
}
123205
}
Binary file not shown.

0 commit comments

Comments
 (0)