Skip to content

Commit 678c4bd

Browse files
authored
[class-parse, generator] Allow showing Kotlin internals via metadata (#793)
Fixes: #790 Kotlin compiled for Java Bytecode is an interesting beast, as it has features which aren't directly supported by Java bytecode. One such feature is visibility: Kotlin supports an [`internal`][0] visibility on types and members: internal class Example Java doesn't have a direct equivalent to `internal`; instead, Java Bytecode uses a visibility of *`public`*: % javap Example.class public final class Example { public Example(); } Commit 439bd83 added support to `Xamarin.Android.Tools.Bytecode.dll` for parsing Kotlin metadata. The result is that Kotlin `internal` are marked as *`private`*, which causes them to be *skipped* and not present within `api.xml`, because `class-parse` [doesn't write out `private` members][1]. The result was that `Metadata.xml` could not be used to mark Kotlin `internal` types as `public`, as they didn't exist within `api.xml`, and thus couldn't serve as a "target" for `Metadata.xml`. Improve support for using `Metadata.xml` to update Kotlin `internal` types by making the following changes: * `XmlClassDeclarationBuilder` now emits *all* types, even `private` types. This includes Kotlin `internal` types which were changed to have `private` visibility. * Kotlin `internal` members are emitted into `api.xml` with a new `//*/@visibility` value of `kotlin-internal`. These changes allow the Kotlin declaration: internal class Example { public fun pubFun() {} internal fun intFun() {} } to be emitted into `api.xml` a'la: <class name="Example" visibility="private" …> <method name="pubFun" visibility="public" …/> <method name="intFun$main" visibility="kotlin-internal" …/> </class> [0]: https://kotlinlang.org/docs/visibility-modifiers.html#modules [0]: https://github.com/xamarin/java.interop/blob/b46598a254c20060b107312564e0ec0aee9e33d6/src/Xamarin.Android.Tools.Bytecode/XmlClassDeclarationBuilder.cs#L32-L33
1 parent cd4c8f8 commit 678c4bd

File tree

7 files changed

+89
-26
lines changed

7 files changed

+89
-26
lines changed

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.Collections;
33
using System.Collections.Generic;
44
using System.Collections.ObjectModel;

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

+34-14
Original file line numberDiff line numberDiff line change
@@ -70,36 +70,56 @@ static void FixupClassVisibility (ClassFile klass, KotlinClass metadata)
7070
// Interfaces should be set to "package-private"
7171
if (klass.AccessFlags.HasFlag (ClassAccessFlags.Interface)) {
7272
Log.Debug ($"Kotlin: Setting internal interface {klass.ThisClass.Name.Value} to package-private");
73-
klass.AccessFlags = SetPackagePrivate (klass.AccessFlags);
73+
klass.AccessFlags = SetVisibility (klass.AccessFlags, null);
7474

7575
foreach (var ic in klass.InnerClasses) {
7676
Log.Debug ($"Kotlin: Setting nested type {ic.InnerClass.Name.Value} in an internal interface to package-private");
77-
ic.InnerClassAccessFlags = SetPackagePrivate (ic.InnerClassAccessFlags);
77+
ic.InnerClassAccessFlags = SetVisibility (ic.InnerClassAccessFlags, null);
7878
}
7979

8080
return;
8181
}
8282

8383
Log.Debug ($"Kotlin: Hiding internal class {klass.ThisClass.Name.Value}");
84-
klass.AccessFlags = ClassAccessFlags.Private;
84+
klass.AccessFlags = SetVisibility (klass.AccessFlags, ClassAccessFlags.Private);
8585

8686
foreach (var ic in klass.InnerClasses) {
8787
Log.Debug ($"Kotlin: Hiding nested internal type {ic.InnerClass.Name.Value}");
88-
ic.InnerClassAccessFlags = ClassAccessFlags.Private;
88+
ic.InnerClassAccessFlags = SetVisibility (ic.InnerClassAccessFlags, ClassAccessFlags.Private);
8989
}
9090

9191
return;
9292
}
9393
}
9494

95-
static ClassAccessFlags SetPackagePrivate (ClassAccessFlags flags)
95+
// Passing null for 'newVisibility' parameter means 'package-private'
96+
static ClassAccessFlags SetVisibility (ClassAccessFlags existing, ClassAccessFlags? newVisibility)
9697
{
97-
// Package-private is stored as "no visibility flags"
98-
flags = (flags ^ ClassAccessFlags.Public) & flags;
99-
flags = (flags ^ ClassAccessFlags.Protected) & flags;
100-
flags = (flags ^ ClassAccessFlags.Private) & flags;
98+
// First we need to remove any existing visibility flags,
99+
// without modifying other flags like Abstract
100+
existing = (existing ^ ClassAccessFlags.Public) & existing;
101+
existing = (existing ^ ClassAccessFlags.Protected) & existing;
102+
existing = (existing ^ ClassAccessFlags.Private) & existing;
101103

102-
return flags;
104+
// Package-private is stored as "no visibility flags", so only add flag if specified
105+
if (newVisibility.HasValue)
106+
existing |= newVisibility.Value;
107+
108+
return existing;
109+
}
110+
111+
static MethodAccessFlags SetVisibility (MethodAccessFlags existing, MethodAccessFlags newVisibility)
112+
{
113+
// First we need to remove any existing visibility flags,
114+
// without modifying other flags like Abstract
115+
existing = (existing ^ MethodAccessFlags.Public) & existing;
116+
existing = (existing ^ MethodAccessFlags.Protected) & existing;
117+
existing = (existing ^ MethodAccessFlags.Private) & existing;
118+
existing = (existing ^ MethodAccessFlags.Internal) & existing;
119+
120+
existing |= newVisibility;
121+
122+
return existing;
103123
}
104124

105125
static void FixupJavaMethods (Methods methods)
@@ -132,7 +152,7 @@ static void FixupConstructor (MethodInfo method, KotlinConstructor metadata)
132152
// Hide constructor if it isn't Public/Protected
133153
if (method.IsPubliclyVisible && !metadata.Flags.IsPubliclyVisible ()) {
134154
Log.Debug ($"Kotlin: Hiding internal constructor {method.DeclaringType?.ThisClass.Name.Value} - {metadata.GetSignature ()}");
135-
method.AccessFlags = MethodAccessFlags.Private;
155+
method.AccessFlags = SetVisibility (method.AccessFlags, MethodAccessFlags.Internal);
136156
}
137157
}
138158

@@ -144,7 +164,7 @@ static void FixupFunction (MethodInfo method, KotlinFunction metadata, KotlinCla
144164
// Hide function if it isn't Public/Protected
145165
if (!metadata.Flags.IsPubliclyVisible ()) {
146166
Log.Debug ($"Kotlin: Hiding internal method {method.DeclaringType?.ThisClass.Name.Value} - {metadata.GetSignature ()}");
147-
method.AccessFlags = MethodAccessFlags.Private;
167+
method.AccessFlags = SetVisibility (method.AccessFlags, MethodAccessFlags.Internal);
148168
return;
149169
}
150170

@@ -190,12 +210,12 @@ static void FixupProperty (MethodInfo getter, MethodInfo setter, KotlinProperty
190210

191211
if (getter?.IsPubliclyVisible == true) {
192212
Log.Debug ($"Kotlin: Hiding internal getter method {getter.DeclaringType?.ThisClass.Name.Value} - {getter.Name}");
193-
getter.AccessFlags = MethodAccessFlags.Private;
213+
getter.AccessFlags = SetVisibility (getter.AccessFlags, MethodAccessFlags.Internal);
194214
}
195215

196216
if (setter?.IsPubliclyVisible == true) {
197217
Log.Debug ($"Kotlin: Hiding internal setter method {setter.DeclaringType?.ThisClass.Name.Value} - {setter.Name}");
198-
setter.AccessFlags = MethodAccessFlags.Private;
218+
setter.AccessFlags = SetVisibility (setter.AccessFlags, MethodAccessFlags.Internal);
199219
}
200220

201221
return;

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

+3
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,9 @@ public enum MethodAccessFlags {
352352
Abstract = 0x0400,
353353
Strict = 0x0800,
354354
Synthetic = 0x1000,
355+
356+
// This is not a real Java MethodAccessFlags, it is used to denote Kotlin "internal" access.
357+
Internal = 0x10000000,
355358
}
356359

357360
[Flags]

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

+7-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.Collections.Generic;
33
using System.Diagnostics;
44
using System.Globalization;
@@ -28,9 +28,6 @@ public XmlClassDeclarationBuilder (ClassFile classFile)
2828

2929
public XElement ToXElement ()
3030
{
31-
var visibility = GetClassVisibility (this.classFile.AccessFlags);
32-
if (visibility == "private")
33-
return null;
3431
return new XElement (GetElementName (),
3532
new XAttribute ("abstract", (classFile.AccessFlags & ClassAccessFlags.Abstract) != 0),
3633
new XAttribute ("deprecated", GetDeprecatedValue (classFile.Attributes)),
@@ -319,7 +316,7 @@ IEnumerable<XElement> GetImplementedInterfaces ()
319316
IEnumerable<XElement> GetConstructors ()
320317
{
321318
return classFile.Methods.Where (m => m.Name == "<init>"
322-
&& (GetMethodVisibility(m.AccessFlags) == "public" || GetMethodVisibility(m.AccessFlags) == "protected"))
319+
&& (GetMethodVisibility(m.AccessFlags) == "public" || GetMethodVisibility(m.AccessFlags) == "protected" || GetMethodVisibility (m.AccessFlags) == "kotlin-internal"))
323320
.OrderBy (m => m.Name + m.Descriptor, StringComparer.OrdinalIgnoreCase)
324321
.Select (c => GetMethod ("constructor", GetThisClassName (), c, null));
325322
}
@@ -374,6 +371,8 @@ static XAttribute GetSynchronized (MethodInfo method)
374371

375372
static string GetVisibility (MethodAccessFlags accessFlags)
376373
{
374+
if (accessFlags.HasFlag (MethodAccessFlags.Internal))
375+
return "kotlin-internal";
377376
if ((accessFlags & MethodAccessFlags.Public) != 0)
378377
return "public";
379378
if ((accessFlags & MethodAccessFlags.Protected) != 0)
@@ -605,6 +604,8 @@ static string GetVisibility (FieldAccessFlags accessFlags)
605604

606605
static string GetMethodVisibility (MethodAccessFlags accessFlags)
607606
{
607+
if (accessFlags.HasFlag (MethodAccessFlags.Internal))
608+
return "kotlin-internal";
608609
if ((accessFlags & MethodAccessFlags.Public) != 0)
609610
return "public";
610611
if ((accessFlags & MethodAccessFlags.Protected) != 0)
@@ -628,7 +629,7 @@ static string GetClassVisibility (ClassAccessFlags accessFlags)
628629
IEnumerable<XElement> GetMethods ()
629630
{
630631
return classFile.Methods.Where (m => !m.Name.StartsWith ("<", StringComparison.OrdinalIgnoreCase)
631-
&& (GetMethodVisibility(m.AccessFlags) == "public" || GetMethodVisibility(m.AccessFlags) == "protected"))
632+
&& (GetMethodVisibility(m.AccessFlags) == "public" || GetMethodVisibility(m.AccessFlags) == "protected" || GetMethodVisibility (m.AccessFlags) == "kotlin-internal"))
632633
.OrderBy (m => m.Name + m.Descriptor, StringComparer.OrdinalIgnoreCase)
633634
.Select (m => GetMethod ("method", m.Name, m,
634635
returns: m.ReturnType.TypeSignature));

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

+12
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ public void HideInternalClass ()
2424

2525
Assert.False (klass.AccessFlags.HasFlag (ClassAccessFlags.Public));
2626
Assert.False (inner_class.InnerClassAccessFlags.HasFlag (ClassAccessFlags.Public));
27+
28+
var output = new XmlClassDeclarationBuilder (klass).ToXElement ().ToString ();
29+
Assert.True (output.Contains ("visibility=\"private\""));
2730
}
2831

2932
[Test]
@@ -58,6 +61,9 @@ public void HideInternalConstructor ()
5861
KotlinFixups.Fixup (new [] { klass });
5962

6063
Assert.False (ctor.AccessFlags.HasFlag (MethodAccessFlags.Public));
64+
65+
var output = new XmlClassDeclarationBuilder (klass).ToXElement ().ToString ();
66+
Assert.True (output.Contains ("visibility=\"kotlin-internal\""));
6167
}
6268

6369
[Test]
@@ -124,6 +130,9 @@ public void HideInternalMethod ()
124130
KotlinFixups.Fixup (new [] { klass });
125131

126132
Assert.False (method.AccessFlags.HasFlag (MethodAccessFlags.Public));
133+
134+
var output = new XmlClassDeclarationBuilder (klass).ToXElement ().ToString ();
135+
Assert.True (output.Contains ("visibility=\"kotlin-internal\""));
127136
}
128137

129138
[Test]
@@ -154,6 +163,9 @@ public void HideInternalProperty ()
154163

155164
Assert.False (getter.AccessFlags.HasFlag (MethodAccessFlags.Public));
156165
Assert.False (setter.AccessFlags.HasFlag (MethodAccessFlags.Public));
166+
167+
var output = new XmlClassDeclarationBuilder (klass).ToXElement ().ToString ();
168+
Assert.True (output.Contains ("visibility=\"kotlin-internal\""));
157169
}
158170

159171
[Test]

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

+22
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Linq;
34
using System.Xml.Linq;
45
using MonoDroid.Generation;
@@ -244,5 +245,26 @@ public void PreserveSourceLineInfo ()
244245
Assert.AreEqual (2, method.LinePosition);
245246
Assert.AreEqual ("obj/Debug/api.xml", method.SourceFile);
246247
}
248+
249+
[Test]
250+
public void IgnoreKotlinInternalMembers ()
251+
{
252+
var xml = XDocument.Parse (@"
253+
<api>
254+
<package name='com.example.test' jni-name='com/example/test'>
255+
<class name='Test' visibility='public'>
256+
<constructor name='Test' visibility='kotlin-internal' />
257+
<method name='DoStuff' visibility='kotlin-internal' />
258+
<field name='MyField' visibility='kotlin-internal' />
259+
</class>
260+
</package>
261+
</api>");
262+
263+
var gens = new Parser (opt).Parse (xml, new List<string> (), "0", 0);
264+
var klass = gens.Single ();
265+
266+
Assert.AreEqual (0, klass.Fields.Count);
267+
Assert.AreEqual (0, klass.Methods.Count);
268+
}
247269
}
248270
}

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

+10-5
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,16 @@ public static ClassGen CreateClass (XElement pkg, XElement elem, CodeGenerationO
3737
klass.AddImplementedInterface (iname);
3838
break;
3939
case "method":
40-
klass.AddMethod (CreateMethod (klass, child, options));
40+
if (child.XGetAttribute ("visibility") != "kotlin-internal")
41+
klass.AddMethod (CreateMethod (klass, child, options));
4142
break;
4243
case "constructor":
43-
klass.Ctors.Add (CreateCtor (klass, child, options));
44+
if (child.XGetAttribute ("visibility") != "kotlin-internal")
45+
klass.Ctors.Add (CreateCtor (klass, child, options));
4446
break;
4547
case "field":
46-
klass.AddField (CreateField (klass, child, options));
48+
if (child.XGetAttribute ("visibility") != "kotlin-internal")
49+
klass.AddField (CreateField (klass, child, options));
4750
break;
4851
case "typeParameters":
4952
break; // handled at GenBaseSupport
@@ -230,10 +233,12 @@ public static InterfaceGen CreateInterface (XElement pkg, XElement elem, CodeGen
230233
iface.AddImplementedInterface (iname);
231234
break;
232235
case "method":
233-
iface.AddMethod (CreateMethod (iface, child, options));
236+
if (child.XGetAttribute ("visibility") != "kotlin-internal")
237+
iface.AddMethod (CreateMethod (iface, child, options));
234238
break;
235239
case "field":
236-
iface.AddField (CreateField (iface, child, options));
240+
if (child.XGetAttribute ("visibility") != "kotlin-internal")
241+
iface.AddField (CreateField (iface, child, options));
237242
break;
238243
case "typeParameters":
239244
break; // handled at GenBaseSupport

0 commit comments

Comments
 (0)