Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 25 additions & 23 deletions src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinFixups.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,13 @@ public static void Fixup (IList<ClassFile> classes)
FixupJavaMethods (c.Methods);

if (metadata.Functions != null) {
foreach (var met in metadata.Functions)
FixupFunction (FindJavaMethod (metadata, met, c), met, class_metadata);
// We work from Java methods to Kotlin metadata because they aren't a 1:1 relation
// and we need to find the "best" match for each Java method.
foreach (var java_method in c.Methods)
if (FindKotlinFunctionMetadata (metadata, java_method) is KotlinFunction function_metadata)
FixupFunction (java_method, function_metadata, class_metadata);
}


if (metadata.Properties != null) {
foreach (var prop in metadata.Properties) {
var getter = FindJavaPropertyGetter (metadata, prop, c);
Expand Down Expand Up @@ -334,40 +336,40 @@ static void FixupField (FieldInfo? field, KotlinProperty metadata)
return null;
}

static MethodInfo? FindJavaMethod (KotlinFile kotlinFile, KotlinFunction function, ClassFile klass)
static KotlinFunction? FindKotlinFunctionMetadata (KotlinFile? kotlinFile, MethodInfo javaMethod)
{
var possible_methods = klass.Methods.Where (method => method.Name == function.JvmName).ToArray ();
var signature = function.JvmSignature;
if (kotlinFile?.Functions is null)
return null;

var java_descriptor = javaMethod.Descriptor;

// If the Descriptor/Signature match, that means all parameters and return type match
if (signature != null && possible_methods.SingleOrDefault (method => method.Descriptor == signature) is MethodInfo m)
return m;
// The method name absolutely has to match
var possible_functions = kotlinFile.Functions.Where (f => f.JvmName == javaMethod.Name).ToArray ();

// If we have metadata with a Descriptor/JvmSignature match, that means all parameters and return type match
if (possible_functions.SingleOrDefault (f => f.JvmSignature != null && f.JvmSignature == java_descriptor) is KotlinFunction kf)
return kf;

// Sometimes JvmSignature is null (or unhelpful), so we're going to construct one ourselves and see if they match
signature = function.ConstructJvmSignature ();
if (possible_functions.SingleOrDefault (f => f.ConstructJvmSignature () == java_descriptor) is KotlinFunction kf2)
return kf2;

if (possible_methods.SingleOrDefault (method => method.Descriptor == signature) is MethodInfo m2)
return m2;
// If that didn't work, let's try it the hard way!
// This catches cases where Kotlin only wrote one metadata entry for multiple methods with the same mangled JvmName (ex: contains-WZ4Q5Ns)
var java_param_count = javaMethod.GetFilteredParameters ().Length;

// If that didn't work, let's do it the hard way!
// I don't know if this catches anything additional, but it was the original code we shipped, so
// we'll keep it just in case something in the wild requires it.
foreach (var method in possible_methods.Where (method => method.GetFilteredParameters ().Length == function.ValueParameters?.Count)) {
foreach (var function in possible_functions.Where (f => f.ValueParameters?.Count == java_param_count)) {
if (function.ReturnType == null)
continue;
if (!TypesMatch (method.ReturnType, function.ReturnType, kotlinFile))
if (!TypesMatch (javaMethod.ReturnType, function.ReturnType, kotlinFile))
continue;

if (!ParametersMatch (kotlinFile, method, function.ValueParameters!))
if (!ParametersMatch (kotlinFile, javaMethod, function.ValueParameters!))
continue;

return method;
return function;
}

// Theoretically this should never be hit, but who knows. At worst, it just means
// Kotlin niceties won't be applied to the method.
Log.Debug ($"Kotlin: Could not find Java method to match '{function.Name} ({function.ConstructJvmSignature ()})'");

return null;
}

Expand Down
22 changes: 22 additions & 0 deletions tests/Xamarin.Android.Tools.Bytecode-Tests/KotlinFixupsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -374,5 +374,27 @@ public void MatchParametersWithStaticThis ()
Assert.AreEqual (1, start);
Assert.AreEqual (2, end);
}

[Test]
public void MatchMetadataToMultipleMethodsWithSameMangledName ()
{
var klass = LoadClassFile ("UByteArray.class");
var meta = GetClassMetadataForClass (klass);

// There is only one Kotlin metadata for Java method "contains-7apg3OU"
var kotlin_function = meta.Functions.Single (m => m.Name == "contains");

// However there are 2 Java methods named "contains-7apg3OU"
// - public boolean contains-7apg3OU (byte element)
// - public static boolean contains-7apg3OU (byte[] arg0, byte element)
var java_methods = klass.Methods.Where (m => m.Name == "contains-7apg3OU");
Assert.AreEqual (2, java_methods.Count ());

KotlinFixups.Fixup (new [] { klass });

// Ensure the fixup "fixed" the parameter "element" type to "ubyte"
Assert.AreEqual ("ubyte", java_methods.ElementAt (0).GetParameters ().Single (p => p.Name == "element").KotlinType);
Assert.AreEqual ("ubyte", java_methods.ElementAt (1).GetParameters ().Single (p => p.Name == "element").KotlinType);
}
}
}