Skip to content

Commit 0065de4

Browse files
jpobstjonpryor
authored andcommitted
[generator] Mark Kotlin-mangled methods as non-virtual (#536)
Fixes: #534 There are cases where Kotlin generates Java methods that are invalid Java identifiers: // Kotlin public open abstract class AbstractUnsigned { public open fun foo(value : UInt) { return 3u } } Results in Java bytecode: public abstract class example.AbstractUnsigned { public abstract void foo-WZ4Q5Ns(int); public example.AbstractUnsigned(); } `foo-WZ4Q5Ns` is not a valid Java identifier, due to the `-`. We already change the name to `Foo()` so C# can compile the method, and JNI allows us to call the method correctly. However we cannot allow the user to override this method because we cannot create the Java override in the JCW. In this case, mark the method as *non*-`virtual` to prevent the user from overriding it: // C# partial class AbstractUnsigned { public void Foo(int value) …} }
1 parent 76d6e6d commit 0065de4

File tree

7 files changed

+110
-37
lines changed

7 files changed

+110
-37
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using System;
2+
using System.Linq;
3+
using System.Xml.Linq;
4+
using Java.Interop.Tools.Generator.Transformation;
5+
using MonoDroid.Generation;
6+
using NUnit.Framework;
7+
8+
namespace generatortests
9+
{
10+
[TestFixture]
11+
public class KotlinFixupsTests
12+
{
13+
[Test]
14+
public void CreateMethod_EnsureKotlinImplFix ()
15+
{
16+
var xml = XDocument.Parse ("<package name=\"com.example.test\" jni-name=\"com/example/test\"><class name=\"test\"><method name=\"add-impl\" final=\"false\" /></class></package>");
17+
var klass = XmlApiImporter.CreateClass (xml.Root, xml.Root.Element ("class"));
18+
19+
KotlinFixups.Fixup (new [] { (GenBase)klass }.ToList ());
20+
21+
Assert.AreEqual ("Add", klass.Methods [0].Name);
22+
Assert.IsTrue (klass.Methods [0].IsFinal);
23+
Assert.IsFalse (klass.Methods [0].IsVirtual);
24+
}
25+
26+
[Test]
27+
public void CreateMethod_EnsureKotlinHashcodeFix ()
28+
{
29+
var xml = XDocument.Parse ("<package name=\"com.example.test\" jni-name=\"com/example/test\"><class name=\"test\"><method name=\"add-h4F1V8i\" final=\"false\" /></class></package>");
30+
var klass = XmlApiImporter.CreateClass (xml.Root, xml.Root.Element ("class"));
31+
32+
KotlinFixups.Fixup (new [] { (GenBase) klass }.ToList ());
33+
34+
Assert.AreEqual ("Add", klass.Methods [0].Name);
35+
Assert.IsTrue (klass.Methods [0].IsFinal);
36+
Assert.IsFalse (klass.Methods [0].IsVirtual);
37+
}
38+
}
39+
}

tests/generator-Tests/Unit-Tests/XmlApiImporterTests.cs

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -89,24 +89,6 @@ public void CreateMethod_EnsureValidNameHyphen ()
8989
Assert.AreEqual ("_3", klass.Methods [0].Name);
9090
}
9191

92-
[Test]
93-
public void CreateMethod_EnsureKotlinImplFix ()
94-
{
95-
var xml = XDocument.Parse ("<package name=\"com.example.test\" jni-name=\"com/example/test\"><class name=\"test\"><method name=\"add-impl\" /></class></package>");
96-
var klass = XmlApiImporter.CreateClass (xml.Root, xml.Root.Element ("class"));
97-
98-
Assert.AreEqual ("Add", klass.Methods [0].Name);
99-
}
100-
101-
[Test]
102-
public void CreateMethod_EnsureKotlinHashcodeFix ()
103-
{
104-
var xml = XDocument.Parse ("<package name=\"com.example.test\" jni-name=\"com/example/test\"><class name=\"test\"><method name=\"add-h4F1V8i\" /></class></package>");
105-
var klass = XmlApiImporter.CreateClass (xml.Root, xml.Root.Element ("class"));
106-
107-
Assert.AreEqual ("Add", klass.Methods [0].Name);
108-
}
109-
11092
[Test]
11193
public void CreateParameter_EnsureValidName ()
11294
{

tools/generator/CodeGenerator.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using Java.Interop.Tools.Diagnostics;
1313
using Java.Interop.Tools.TypeNameMappings;
1414
using MonoDroid.Generation.Utilities;
15+
using Java.Interop.Tools.Generator.Transformation;
1516

1617
namespace Xamarin.Android.Binder
1718
{
@@ -149,6 +150,9 @@ static void Run (CodeGeneratorOptions options, DirectoryAssemblyResolver resolve
149150
AddTypeToTable (opt, gen);
150151
}
151152

153+
// Apply fixups
154+
KotlinFixups.Fixup (gens);
155+
152156
Validate (gens, opt, new CodeGeneratorContext ());
153157

154158
if (api_versions_xml != null)

tools/generator/Java.Interop.Tools.Generator.Importers/XmlApiImporter.cs

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -245,25 +245,8 @@ public static Method CreateMethod (GenBase declaringType, XElement elem)
245245

246246
if (elem.Attribute ("managedName") != null)
247247
method.Name = elem.XGetAttribute ("managedName");
248-
else {
249-
var name = method.JavaName;
250-
251-
// Kotlin generates methods that cannot be referenced in Java,
252-
// like `add-impl` and `add-V5j3Lk8`. We mangle them back into
253-
// something a user would expect by truncating anything after the hyphen.
254-
var index = name.IndexOf ("-impl");
255-
256-
if (index >= 0)
257-
name = name.Substring (0, index);
258-
259-
index = name.IndexOf ('-');
260-
261-
// `add-V5j3Lk8` is always a 7 character hashcode
262-
if (index >= 0 && name.Length - index == 8)
263-
name = name.Substring (0, index);
264-
265-
method.Name = StringRocks.MemberToPascalCase (name);
266-
}
248+
else
249+
method.Name = StringRocks.MemberToPascalCase (method.JavaName);
267250

268251
if (method.IsReturnEnumified) {
269252
method.ManagedReturn = elem.XGetAttribute ("enumReturn");

tools/generator/Java.Interop.Tools.Generator.ObjectModel/MethodBase.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,24 @@ public string GetSignature (CodeGenerationOptions opt)
7979

8080
public virtual bool IsGeneric => Parameters.HasGeneric;
8181

82+
public bool IsKotlinNameMangled {
83+
get {
84+
// Kotlin generates methods that cannot be referenced in Java,
85+
// like `add-impl` and `add-V5j3Lk8`. We will need to fix those later.
86+
if (this is Method method) {
87+
if (method.JavaName.IndexOf ("-impl") >= 0)
88+
return true;
89+
90+
var index = method.JavaName.IndexOf ('-');
91+
92+
// `add-V5j3Lk8` is always a 7 character hashcode
93+
return index >= 0 && method.JavaName.Length - index == 8;
94+
}
95+
96+
return false;
97+
}
98+
}
99+
82100
public virtual bool Matches (MethodBase other)
83101
{
84102
if (Name != other.Name)
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using MonoDroid.Generation;
5+
using MonoDroid.Utils;
6+
7+
namespace Java.Interop.Tools.Generator.Transformation
8+
{
9+
public static class KotlinFixups
10+
{
11+
public static void Fixup (List<GenBase> gens)
12+
{
13+
foreach (var c in gens.OfType<ClassGen> ())
14+
FixupClass (c);
15+
}
16+
17+
private static void FixupClass (ClassGen c)
18+
{
19+
// Kotlin mangles the name of some methods to make them
20+
// inaccessible from Java, like `add-impl` and `add-V5j3Lk8`.
21+
// We need to generate C# compatible names as well as prevent overriding
22+
// them as we cannot generate JCW for them.
23+
var invalid_methods = c.Methods.Where (m => m.IsKotlinNameMangled).ToList ();
24+
25+
foreach (var method in invalid_methods) {
26+
27+
// If the method is virtual, mark it as !virtual as it can't be overridden in Java
28+
if (!method.IsFinal)
29+
method.IsFinal = true;
30+
31+
if (method.IsVirtual)
32+
method.IsVirtual = false;
33+
34+
// Only run this if it's the default name (ie: not a user's "managedName")
35+
if (method.Name == StringRocks.MemberToPascalCase (method.JavaName).Replace ('-', '_')) {
36+
// We want to remove the hyphen and anything afterwards to fix mangled names,
37+
// but a previous step converted it to an underscore. Remove the final
38+
// underscore and anything after it.
39+
var index = method.Name.LastIndexOf ('_');
40+
41+
method.Name = method.Name.Substring (0, index);
42+
}
43+
}
44+
}
45+
}
46+
}

tools/generator/generator.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@
104104
<Compile Include="Java.Interop.Tools.Generator.ObjectModel\NamespaceMapping.cs" />
105105
<Compile Include="Java.Interop.Tools.Generator.ObjectModel\Parameter.cs" />
106106
<Compile Include="Java.Interop.Tools.Generator.ObjectModel\ParameterList.cs" />
107+
<Compile Include="Java.Interop.Tools.Generator.Transformation\KotlinFixups.cs" />
107108
<Compile Include="Java.Interop.Tools.Generator.Transformation\Parser.cs" />
108109
<Compile Include="Utilities\AncestorDescendantCache.cs" />
109110
<Compile Include="Utilities\ProcessRocks.cs" />

0 commit comments

Comments
 (0)