Skip to content

Commit 4ef5081

Browse files
authored
[Xamarin.Android.Tools.Bytecode] hide Kotlin internal nested types (#827)
Fixes: #826 Context: #682 Given the following Kotlin code: internal class MyClass { public interface MyInterface { } } The default Java representation of this would be: public MyClass { public MyInterface { } } In order to prevent this from being bound, our Kotlin fixups attempted to change the Java representation to: private class MyClass { private interface MyInterface { } } However there was a bug preventing the internal interface from being marked as `private`, because we are setting the visibility on the parent type's `InnerClassAccessFlags`, *not* the nested type itself. When we output the XML we use use the declaring class' `InnerClassAccessFlags`, we instead look at the flags on the nested type's `.class` file, which was still `public`: <class name="MyClass" visibility="private" … /> <class name="MyClass.MyInterface" visibility="public" … /> This would result in a `generator` warning when trying to bind `MyClass.MyInterface`, as the parent `MyClass` type is skipped: warning BG8604: top ancestor MyClass not found for nested type MyClass.MyInterface Fix this by finding the appropriate inner class `.class` file and updating the access flags there, recursively: <class name="MyClass" visibility="private" … /> <class name="MyClass.MyInterface" visibility="private" … /> Note: the [`InnerClasses` Attribute][0] for an inner class may contain the declaring class. For example, the `InnerClasses` for `InternalClassWithNestedInterface$NestedInterface` contains `InternalClassWithNestedInterface$NestedInterface`. We must thus protect recursive `HideInternalInnerClass()` invocations from processing the current type, to avoid infinite recursion. [0]: https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.6
1 parent df4c5e7 commit 4ef5081

6 files changed

+48
-6
lines changed

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

+24-6
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public static void Fixup (IList<ClassFile> classes)
3030
var class_metadata = (metadata as KotlinClass);
3131

3232
if (class_metadata != null) {
33-
FixupClassVisibility (c, class_metadata);
33+
FixupClassVisibility (c, class_metadata, classes);
3434

3535
if (!c.AccessFlags.IsPubliclyVisible ())
3636
continue;
@@ -62,7 +62,7 @@ public static void Fixup (IList<ClassFile> classes)
6262
}
6363
}
6464

65-
static void FixupClassVisibility (ClassFile klass, KotlinClass metadata)
65+
static void FixupClassVisibility (ClassFile klass, KotlinClass metadata, IList<ClassFile> classes)
6666
{
6767
// Hide class if it isn't Public/Protected
6868
if (klass.AccessFlags.IsPubliclyVisible () && !metadata.Visibility.IsPubliclyVisible ()) {
@@ -83,15 +83,33 @@ static void FixupClassVisibility (ClassFile klass, KotlinClass metadata)
8383
Log.Debug ($"Kotlin: Hiding internal class {klass.ThisClass.Name.Value}");
8484
klass.AccessFlags = SetVisibility (klass.AccessFlags, ClassAccessFlags.Private);
8585

86-
foreach (var ic in klass.InnerClasses) {
87-
Log.Debug ($"Kotlin: Hiding nested internal type {ic.InnerClass.Name.Value}");
88-
ic.InnerClassAccessFlags = SetVisibility (ic.InnerClassAccessFlags, ClassAccessFlags.Private);
89-
}
86+
foreach (var ic in klass.InnerClasses)
87+
HideInternalInnerClass (ic, classes);
9088

9189
return;
9290
}
9391
}
9492

93+
static void HideInternalInnerClass (InnerClassInfo klass, IList<ClassFile> classes)
94+
{
95+
var existing = klass.InnerClassAccessFlags;
96+
97+
klass.InnerClassAccessFlags = SetVisibility (existing, ClassAccessFlags.Private);
98+
Log.Debug ($"Kotlin: Hiding nested internal type {klass.InnerClass.Name.Value}");
99+
100+
// Setting the inner class access flags above doesn't technically do anything, because we output
101+
// from the inner class's ClassFile, so we need to find that and set it there.
102+
var kf = classes.FirstOrDefault (c => c.ThisClass.Name.Value == klass.InnerClass.Name.Value);
103+
104+
if (kf != null) {
105+
kf.AccessFlags = SetVisibility (kf.AccessFlags, ClassAccessFlags.Private);
106+
kf.InnerClass.InnerClassAccessFlags = SetVisibility (kf.InnerClass.InnerClassAccessFlags, ClassAccessFlags.Private);
107+
108+
foreach (var inner_class in kf.InnerClasses.Where (c => c.OuterClass.Name.Value == kf.ThisClass.Name.Value))
109+
HideInternalInnerClass (inner_class, classes);
110+
}
111+
}
112+
95113
// Passing null for 'newVisibility' parameter means 'package-private'
96114
static ClassAccessFlags SetVisibility (ClassAccessFlags existing, ClassAccessFlags? newVisibility)
97115
{

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

+19
Original file line numberDiff line numberDiff line change
@@ -310,5 +310,24 @@ public void HandleKotlinNameShadowing ()
310310
Assert.True (klass.Methods.Single (m => m.Name == "hitCount").AccessFlags.HasFlag (MethodAccessFlags.Public));
311311
Assert.True (klass.Methods.Single (m => m.Name == "setType").AccessFlags.HasFlag (MethodAccessFlags.Public));
312312
}
313+
314+
[Test]
315+
public void HidePublicInterfaceInInternalClass ()
316+
{
317+
var klass = LoadClassFile ("InternalClassWithNestedInterface.class");
318+
var inner_interface = LoadClassFile ("InternalClassWithNestedInterface$NestedInterface.class");
319+
var inner_inner_interface = LoadClassFile ("InternalClassWithNestedInterface$NestedInterface$DoubleNestedInterface.class");
320+
321+
KotlinFixups.Fixup (new [] { klass, inner_interface, inner_inner_interface });
322+
323+
var output = new XmlClassDeclarationBuilder (klass).ToXElement ().ToString ();
324+
Assert.True (output.Contains ("visibility=\"public\""));
325+
326+
var output2 = new XmlClassDeclarationBuilder (inner_interface).ToXElement ().ToString ();
327+
Assert.True (output2.Contains ("visibility=\"private\""));
328+
329+
var output3 = new XmlClassDeclarationBuilder (inner_inner_interface).ToXElement ().ToString ();
330+
Assert.True (output3.Contains ("visibility=\"private\""));
331+
}
313332
}
314333
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
internal class InternalClassWithNestedInterface {
2+
public interface NestedInterface {
3+
public interface DoubleNestedInterface
4+
}
5+
}

0 commit comments

Comments
 (0)