Skip to content

Commit 06214ff

Browse files
authored
[Xamain.Android.Tools.Bytecode] Hide private Kotlin default ctors (#1206)
Context: f6c12ba `Kotlin.Stdlib` contains [`kotlin.io.encoding.Base64`][0], a public class which is not intended to be instantiated by external users. Thus it only contains a `private` default constructor: // Kotlin public open /* partial */ class Base64 private constructor( internal val isUrlSafe: Boolean, internal val isMimeScheme: Boolean ) { // … public companion object Default : Base64(isUrlSafe = false, isMimeScheme = false) { // … } } which compiles into the equivalent Java code: // Java public /* partial */ class Base64 { private Base64 (boolean isUrlSafe, boolean isMimeScheme) {…} public Base64(boolean isUrlSafe, boolean isMimeScheme, kotlin.jvm.internal.DefaultConstructorMarker $constructor_marker) {…} // ^ synthetic constructor } Previously (f6c12ba) we believed that a synthetic default constructor would always end in an `int, DefaultConstructorMarker)` parameter pair, however this one does not. This synthetic constructor causes us to generate some bizarre "usable but shouldn't be used" constructors: // C# partial class Base64 { [Register (".ctor", "(ZZLkotlin/jvm/internal/DefaultConstructorMarker;)V", "")] public unsafe Base64 (bool isUrlSafe, bool isMimeScheme, global::Kotlin.Jvm.Internal.DefaultConstructorMarker? _constructor_marker) : base (IntPtr.Zero, JniHandleOwnership.DoNotTransfer) { // … } } Fix this by extending our Kotlin default constructor marker detection logic to look for any synthetic constructor whose final parameter is `DefaultConstructorMarker` to handle this additional case. With the change, `Kotlin.IO.Encoding.Base64` no longer generates a `public` constructor. [0]: https://github.com/JetBrains/kotlin/blob/3fbb7bc92086bdf3bde123a8f774bce25b19ef37/libraries/stdlib/src/kotlin/io/encoding/Base64.kt
1 parent cdff2b2 commit 06214ff

File tree

5 files changed

+31
-6
lines changed

5 files changed

+31
-6
lines changed

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

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,7 @@ public static string GetMethodNameWithoutUnsignedSuffix (string name)
9797

9898
public static bool IsDefaultConstructorMarker (this MethodInfo method)
9999
{
100-
// A default constructor is synthetic and always has an int and a
101-
// DefaultConstructorMarker as its final 2 parameters.
100+
// A default constructor is synthetic and has a DefaultConstructorMarker as its final parameter.
102101
if (method.Name != "<init>")
103102
return false;
104103

@@ -107,12 +106,11 @@ public static bool IsDefaultConstructorMarker (this MethodInfo method)
107106

108107
var parameters = method.GetParameters ();
109108

110-
if (parameters.Length < 2)
109+
if (parameters.Length < 1)
111110
return false;
112111

113-
// Parameter list ends with `int, DefaultConstructorMarker`.
114-
return parameters [parameters.Length - 2].Type.TypeSignature == "I" &&
115-
parameters [parameters.Length - 1].Type.TypeSignature == "Lkotlin/jvm/internal/DefaultConstructorMarker;";
112+
// Parameter list ends with `DefaultConstructorMarker`.
113+
return parameters [parameters.Length - 1].Type.TypeSignature == "Lkotlin/jvm/internal/DefaultConstructorMarker;";
116114
}
117115

118116
// Sometimes the Kotlin provided JvmSignature is null (or unhelpful), so we need to construct one ourselves

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,28 @@ public void HideDefaultConstructorMarker ()
9292
Assert.False (ctor_3p.AccessFlags.HasFlag (MethodAccessFlags.Public));
9393
}
9494

95+
[Test]
96+
public void HidePrivateDefaultConstructorMarker ()
97+
{
98+
var klass = LoadClassFile ("PrivateDefaultConstructor.class");
99+
100+
// init (int isFoo)
101+
var ctor_1p = klass.Methods.Single (m => m.Name == "<init>" && m.GetParameters ().Length == 1);
102+
103+
// init (int isFoo, DefaultConstructorMarker p1)
104+
var ctor_2p = klass.Methods.Single (m => m.Name == "<init>" && m.GetParameters ().Length == 2);
105+
106+
Assert.True (ctor_2p.AccessFlags.HasFlag (MethodAccessFlags.Public));
107+
108+
KotlinFixups.Fixup ([klass]);
109+
110+
// Assert that the normal constructor is still public
111+
Assert.False (ctor_1p.AccessFlags.HasFlag (MethodAccessFlags.Public));
112+
113+
// Assert that the synthetic "DefaultConstructorMarker" constructor has been marked private
114+
Assert.False (ctor_2p.AccessFlags.HasFlag (MethodAccessFlags.Public));
115+
}
116+
95117
[Test]
96118
public void HideImplementationMethod ()
97119
{
Binary file not shown.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
public open class PrivateDefaultConstructor private constructor (internal val isFoo: Boolean) {
2+
init { }
3+
4+
public companion object Default : PrivateDefaultConstructor (isFoo = false) { }
5+
}

0 commit comments

Comments
 (0)