Skip to content

Commit

Permalink
[Xamarin.Android.Tools.Bytecode] Hide Kotlin synthetic constructors (#…
Browse files Browse the repository at this point in the history
…700)

Fixes: #694

Context: dotnet/android#3776

When using a Kotlin default constructor like:

	class MaterialDialog(
	  val windowContext: Context,
	  val dialogBehavior: DialogBehavior = DEFAULT_BEHAVIOR
	) : Dialog(windowContext, inferTheme(windowContext, dialogBehavior)) 
	{ ... }

Kotlin will create 2 constructors, the "real" one, and a synthetic one
denoting which constructor is the default constructor using a
`kotlin.jvm.internal.DefaultConstructorMarker` parameter:

	<constructor deprecated="not deprecated" final="false"
	    name="MaterialDialog" static="false" visibility="public"
	    bridge="false" synthetic="false"
	    jni-signature="(Landroid/content/Context;Lcom/afollestad/materialdialogs/DialogBehavior;)V">
	  <parameter name="windowContext" type="android.content.Context" jni-type="Landroid/content/Context;" not-null="true"/>
	  <parameter name="dialogBehavior" type="com.afollestad.materialdialogs.DialogBehavior" jni-type="Lcom/afollestad/materialdialogs/DialogBehavior;" not-null="true"/>
	</constructor>
	<constructor deprecated="not deprecated" final="false"
	    name="MaterialDialog" static="false" visibility="public"
	    bridge="false" synthetic="true"
	    jni-signature="(Landroid/content/Context;Lcom/afollestad/materialdialogs/DialogBehavior;ILkotlin/jvm/internal/DefaultConstructorMarker;)V">
	  <parameter name="p0" type="android.content.Context" jni-type="Landroid/content/Context;"/>
	  <parameter name="p1" type="com.afollestad.materialdialogs.DialogBehavior" jni-type="Lcom/afollestad/materialdialogs/DialogBehavior;"/>
	  <parameter name="p2" type="int" jni-type="I"/>
	  <parameter name="p3" type="kotlin.jvm.internal.DefaultConstructorMarker" jni-type="Lkotlin/jvm/internal/DefaultConstructorMarker;"/>
	</constructor>

Additionally, the `kotlin.jvm.internal.DefaultConstructorMarker` type
is not available in the `Xamarin.Kotlin.StdLib` NuGet package, because
the type is `internal`.

Consequently, when trying to bind the constructor, `ApiXmlAdjuster`
reports this "error":

	Error while processing '[Constructor] MaterialDialog(android.content.Context p0, com.afollestad.materialdialogs.DialogBehavior p1, int p2, kotlin.jvm.internal.DefaultConstructorMarker p3)' in '[Class] com.afollestad.materialdialogs.MaterialDialog': Type 'kotlin.jvm.internal.DefaultConstructorMarker' was not found.

This is actually good, as we shouldn't bind this synthetic constructor,
but we should detect this Kotlin-ism and not emit the "error", as it
misleads users into thinking they need to do something to fix it.

Update `Xamarin.Android.Tools.Bytecode` to *hide* constructors for
which the final two parameter types are `int, DefaultConstructorMarker`,
thus ensuring that `ApiXmlAdjuster` & co. won't process such members
or attempt to resolve the unresolvable `DefaultConstructorMarker` type.
  • Loading branch information
jpobst authored Aug 28, 2020
1 parent 0334f42 commit f6c12ba
Show file tree
Hide file tree
Showing 7 changed files with 56 additions and 38 deletions.
6 changes: 6 additions & 0 deletions src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinFixups.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ static void FixupJavaMethods (Methods methods)
method.AccessFlags = MethodAccessFlags.Private;
}

// Hide constructor if it's the synthetic DefaultConstructorMarker one
foreach (var method in methods.Where (method => method.IsDefaultConstructorMarker ())) {
Log.Debug ($"Kotlin: Hiding synthetic default constructor in class '{method.DeclaringType?.ThisClass.Name.Value}' with signature '{method.Descriptor}'");
method.AccessFlags = ((method.AccessFlags ^ MethodAccessFlags.Public) & method.AccessFlags) | MethodAccessFlags.Private;
}

// Better parameter names in extension methods
foreach (var method in methods.Where (m => m.IsPubliclyVisible && m.AccessFlags.HasFlag (MethodAccessFlags.Static)))
FixupExtensionMethod (method);
Expand Down
20 changes: 20 additions & 0 deletions src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,26 @@ public static string GetMethodNameWithoutSuffix (this MethodInfo method)
return index >= 0 ? method.Name.Substring (0, index) : method.Name;
}

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

if (!method.AccessFlags.HasFlag (MethodAccessFlags.Synthetic))
return false;

var parameters = method.GetParameters ();

if (parameters.Length < 2)
return false;

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

public static bool IsPubliclyVisible (this ClassAccessFlags flags) => flags.HasFlag (ClassAccessFlags.Public) || flags.HasFlag (ClassAccessFlags.Protected);

public static bool IsPubliclyVisible (this KotlinClassVisibility flags) => flags == KotlinClassVisibility.Public || flags == KotlinClassVisibility.Protected;
Expand Down
26 changes: 26 additions & 0 deletions tests/Xamarin.Android.Tools.Bytecode-Tests/KotlinFixupsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,32 @@ public void HideInternalConstructor ()
Assert.False (ctor.AccessFlags.HasFlag (MethodAccessFlags.Public));
}

[Test]
public void HideDefaultConstructorMarker ()
{
var klass = LoadClassFile ("DefaultConstructor.class");

// init ()
var ctor_0p = klass.Methods.Single (m => m.Name == "<init>" && m.GetParameters ().Length == 0);

// init (string name)
var ctor_1p = klass.Methods.Single (m => m.Name == "<init>" && m.GetParameters ().Length == 1);

// init (string p0, int p1, DefaultConstructorMarker p2)
var ctor_3p = klass.Methods.Single (m => m.Name == "<init>" && m.GetParameters ().Length == 3);

Assert.True (ctor_3p.AccessFlags.HasFlag (MethodAccessFlags.Public));

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

// Assert that the normal constructors are still public
Assert.True (ctor_0p.AccessFlags.HasFlag (MethodAccessFlags.Public));
Assert.True (ctor_1p.AccessFlags.HasFlag (MethodAccessFlags.Public));

// Assert that the synthetic "DefaultConstructorMarker" constructor has been marked private
Assert.False (ctor_3p.AccessFlags.HasFlag (MethodAccessFlags.Public));
}

[Test]
public void HideImplementationMethod ()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,44 +75,6 @@
type="boolean"
jni-type="Z" />
</constructor>
<constructor
deprecated="not deprecated"
final="false"
name="JvmOverloadsConstructor"
static="false"
visibility="public"
bridge="false"
synthetic="true"
jni-signature="(LJvmOverloadsConstructor;IILjava/lang/String;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V">
<parameter
name="p0"
type="JvmOverloadsConstructor"
jni-type="LJvmOverloadsConstructor;" />
<parameter
name="p1"
type="int"
jni-type="I" />
<parameter
name="p2"
type="int"
jni-type="I" />
<parameter
name="p3"
type="java.lang.String"
jni-type="Ljava/lang/String;" />
<parameter
name="p4"
type="boolean"
jni-type="Z" />
<parameter
name="p5"
type="int"
jni-type="I" />
<parameter
name="p6"
type="kotlin.jvm.internal.DefaultConstructorMarker"
jni-type="Lkotlin/jvm/internal/DefaultConstructorMarker;" />
</constructor>
<constructor
deprecated="not deprecated"
final="false"
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class DefaultConstructor (name: String = "bob") {
}
2 changes: 2 additions & 0 deletions tools/generator/generator.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
"src\\Java.Interop.Tools.Diagnostics\\Java.Interop.Tools.Diagnostics.csproj",
"src\\Xamarin.Android.Tools.AnnotationSupport\\Xamarin.Android.Tools.AnnotationSupport.csproj",
"src\\Xamarin.Android.Tools.ApiXmlAdjuster\\Xamarin.Android.Tools.ApiXmlAdjuster.csproj",
"src\\Xamarin.Android.Tools.Bytecode\\Xamarin.Android.Tools.Bytecode.csproj",
"src\\Xamarin.SourceWriter\\Xamarin.SourceWriter.csproj",
"tests\\generator-Tests\\generator-Tests.csproj",
"tests\\Xamarin.Android.Tools.Bytecode-Tests\\Xamarin.Android.Tools.Bytecode-Tests.csproj",
"tests\\Xamarin.SourceWriter-Tests\\Xamarin.SourceWriter-Tests.csproj",
"tools\\generator\\generator.csproj",
]
Expand Down

0 comments on commit f6c12ba

Please sign in to comment.