Skip to content
This repository was archived by the owner on Jul 6, 2026. It is now read-only.

Commit 4b266fa

Browse files
authored
[Xamarin.Android.Tools.Bytecode] Support @jvmoverloads (#651)
@moljac ran into an interesting warning from `class-parse` when binding [OfficeUIFrabric][0]: warning : class-parse: warning: method com/microsoft/officeuifabric/widget/BottomNavigationView.<init>(Landroid/content/Context;)V: Local variables array has 0 entries ('LocalVariableTableAttribute(LocalVariableTableEntry(Name='context', Descriptor='Landroid/content/Context;'))'); descriptor has 1 entries! For starters, this shouldn't be a warning; it's not actionable. There is nothing a user can do to fix this warning; it's only meaningful to the Java.Interop team. Update `Xamarin.Android.Tools.Bytecode.MethodInfo.GetParameters()` so that these messages are *debug* messages, not warnings. `class-parse` knows how many parameters are present based on the JNI method descriptor, in this case `(Landroid/content/Context;)V`, which specifies one parameter type. The JNI descriptor *doesn't* contain parameter name information; where do parameter names come from? If the `.class` file was built via `javac -parameters`, then the `MethodParametersAttribute` blob can be used; see 4273e5c. However, before checking for `MethodParametersAttribute`, `class-parse` will attempt to use the `LocalVariableTableAttribute` and `LocalVariableTableEntry` values to infer parameter names. As part of this inference, method parameters are assumed to be variables with `LocalVariableTableEntry.StartPC` is 0; from `class-parse --dump BottomNavigationView.class`: LocalVariableTableAttribute( LocalVariableTableEntry(Name='context', Descriptor='Landroid/content/Context;', StartPC=0, Index=1)) Next, `class-parse` will "skip" the first variable for instance methods. This results in "skipping" the `context` variable, resulting in the message: Local variables array has 0 entries …; descriptor has 1 entries! To fix this, `class-parse` needs a stricter "skip the `this` parameter" check: the first parameter should *only* be skipped when: 1. The method is an instance method, not a `static` method, *and* 2. The JNI descriptor of the first parameter is the same as the descriptor of the declaring type. Additionally, update the code style to use `enumValue.HasFlag(V)` instead of `(enumValue & V) == V` for readability. Finally, why was this failing in the first place? In some circumstances, when the Kotlin [`@JvmOverloads`][2] annotation is used on a `constructor`, Kotlin will emit all possible overloads for the constructor, and in many of those overloads the `this` parameter *won't* be present in the `LocalVariableTableAttribute` data, as seen above with `BottomNavigationView`. [0]: https://jcenter.bintray.com/com/microsoft/uifabric/OfficeUIFabric/ [1]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.jvm/-jvm-overloads/
1 parent b00e644 commit 4b266fa

8 files changed

Lines changed: 284 additions & 7 deletions

File tree

‎src/Xamarin.Android.Tools.Bytecode/AttributeInfo.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,7 @@ public string Descriptor {
429429

430430
publicoverridestringToString()
431431
{
432-
returnstring.Format("LocalVariableTableEntry(Name='{0}', Descriptor='{1}')",Name,Descriptor);
432+
return$"LocalVariableTableEntry(Name='{Name}', Descriptor='{Descriptor}', StartPC={StartPC}, Index={Index})";
433433
}
434434
}
435435

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ public string PackageName {
9898
}
9999
}
100100

101+
publicstringFullJniName=>"L"+ThisClass.Name.Value+";";
102+
101103
publicstringSourceFileName{
102104
get{
103105
varsourceFile=Attributes.Get<SourceFileAttribute>();

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

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,17 +90,21 @@ public ParameterInfo[] GetParameters ()
9090
if(locals!=null){
9191
varnames=locals.LocalVariables.Where(p =>p.StartPC==0).ToList();
9292
intstart=0;
93-
if((AccessFlags&MethodAccessFlags.Static)==0)
93+
if(names.Count!=parameters.Length&&
94+
!AccessFlags.HasFlag(MethodAccessFlags.Static)&&
95+
names.Count>start&&
96+
names[start].Descriptor==DeclaringType.FullJniName){
9497
start++;// skip `this` parameter
98+
}
9599
if(!DeclaringType.IsStatic&&
96100
names.Count>start&&
97101
(parameters.Length==0||parameters[0].Type.BinaryName!=names[start].Descriptor)){
98102
start++;// JDK 8?
99103
}
100-
if(((AccessFlags&MethodAccessFlags.Synthetic)!=MethodAccessFlags.Synthetic)&&
104+
if(!AccessFlags.HasFlag(MethodAccessFlags.Synthetic)&&
101105
((names.Count-start)!=parameters.Length)&&
102106
!enumCtor){
103-
Log.Warning(1,"class-parse: warning: method {0}.{1}{2}: "+
107+
Log.Debug("class-parse: method {0}.{1}{2}: "+
104108
"Local variables array has {3} entries ('{4}'); descriptor has {5} entries!",
105109
DeclaringType.ThisClass.Name.Value,Name,Descriptor,
106110
names.Count-start,
@@ -111,7 +115,7 @@ public ParameterInfo[] GetParameters ()
111115
for(inti=0;i<max;++i){
112116
parameters[i].Name=names[start+i].Name;
113117
if(parameters[i].Type.BinaryName!=names[start+i].Descriptor){
114-
Log.Warning(1,"class-parse: warning: method {0}.{1}{2}: "+
118+
Log.Debug("class-parse: method {0}.{1}{2}: "+
115119
"Local variable type descriptor mismatch! Got '{3}'; expected '{4}'.",
116120
DeclaringType.ThisClass.Name.Value,Name,Descriptor,
117121
parameters[i].Type.BinaryName,
@@ -122,7 +126,7 @@ public ParameterInfo[] GetParameters ()
122126
varsig=GetSignature();
123127
if(sig!=null){
124128
if((sig.Parameters.Count!=parameters.Length)&&!enumCtor){
125-
Log.Warning(1,"class-parse: warning: method {0}.{1}{2}: "+
129+
Log.Debug("class-parse: method {0}.{1}{2}: "+
126130
"Signature ('{3}') has {4} entries; Descriptor '{5}' has {6} entries!",
127131
DeclaringType.ThisClass.Name.Value,Name,Descriptor,
128132
Attributes.Get<SignatureAttribute>(),

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public XElement ToXElement ()
3939
GetExtendsGenericAware(),
4040
newXAttribute("final",(classFile.AccessFlags&ClassAccessFlags.Final)!=0),
4141
newXAttribute("name",GetThisClassName()),
42-
newXAttribute("jni-signature","L"+classFile.ThisClass.Name.Value+";"),
42+
newXAttribute("jni-signature",classFile.FullJniName),
4343
GetSourceFile(),
4444
newXAttribute("static",classFile.IsStatic),
4545
newXAttribute("visibility",GetVisibility(classFile.Visibility)),
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
usingSystem;
2+
usingSystem.Collections.Generic;
3+
usingSystem.Linq;
4+
5+
usingXamarin.Android.Tools.Bytecode;
6+
7+
usingNUnit.Framework;
8+
9+
usingAssembly=System.Reflection.Assembly;
10+
11+
namespaceXamarin.Android.Tools.BytecodeTests{
12+
13+
[TestFixture]
14+
publicclassJvmOverloadsConstructorTests:ClassFileFixture{
15+
16+
conststringClassFile="JvmOverloadsConstructor.class";
17+
conststringXmlFile="JvmOverloadsConstructor.xml";
18+
19+
[Test]
20+
publicvoidClassFile_WithJavaType_class()
21+
{
22+
varc=LoadClassFile(ClassFile);
23+
newExpectedTypeDeclaration{
24+
MajorVersion=0x32,
25+
MinorVersion=0,
26+
ConstantPoolCount=59,
27+
AccessFlags=ClassAccessFlags.Public|ClassAccessFlags.Final|ClassAccessFlags.Super,
28+
FullName="JvmOverloadsConstructor",
29+
Superclass=newTypeInfo("java/lang/Object","Ljava/lang/Object;"),
30+
Methods={
31+
newExpectedMethodDeclaration{
32+
Name="<init>",
33+
AccessFlags=MethodAccessFlags.Public,
34+
ReturnDescriptor="V",
35+
Parameters={
36+
newParameterInfo("something","LJvmOverloadsConstructor;","LJvmOverloadsConstructor;"),
37+
newParameterInfo("id","I","I"),
38+
newParameterInfo("imageId","I","I"),
39+
newParameterInfo("title","Ljava/lang/String;","Ljava/lang/String;"),
40+
newParameterInfo("useDivider","Z","Z"),
41+
},
42+
},
43+
newExpectedMethodDeclaration{
44+
Name="<init>",
45+
AccessFlags=MethodAccessFlags.Public|MethodAccessFlags.Synthetic,
46+
ReturnDescriptor="V",
47+
Parameters={
48+
newParameterInfo("p0","LJvmOverloadsConstructor;","LJvmOverloadsConstructor;"),
49+
newParameterInfo("p1","I","I"),
50+
newParameterInfo("p2","I","I"),
51+
newParameterInfo("p3","Ljava/lang/String;","Ljava/lang/String;"),
52+
newParameterInfo("p4","Z","Z"),
53+
newParameterInfo("p5","I","I"),
54+
newParameterInfo("p6","Lkotlin/jvm/internal/DefaultConstructorMarker;","Lkotlin/jvm/internal/DefaultConstructorMarker;"),
55+
},
56+
},
57+
newExpectedMethodDeclaration{
58+
Name="<init>",
59+
AccessFlags=MethodAccessFlags.Public,
60+
ReturnDescriptor="V",
61+
Parameters={
62+
newParameterInfo("something","LJvmOverloadsConstructor;","LJvmOverloadsConstructor;"),
63+
newParameterInfo("id","I","I"),
64+
newParameterInfo("imageId","I","I"),
65+
newParameterInfo("title","Ljava/lang/String;","Ljava/lang/String;"),
66+
},
67+
},
68+
newExpectedMethodDeclaration{
69+
Name="<init>",
70+
AccessFlags=MethodAccessFlags.Public,
71+
ReturnDescriptor="V",
72+
Parameters={
73+
newParameterInfo("something","LJvmOverloadsConstructor;","LJvmOverloadsConstructor;"),
74+
newParameterInfo("id","I","I"),
75+
newParameterInfo("title","Ljava/lang/String;","Ljava/lang/String;"),
76+
},
77+
},
78+
newExpectedMethodDeclaration{
79+
Name="<init>",
80+
AccessFlags=MethodAccessFlags.Public,
81+
ReturnDescriptor="V",
82+
Parameters={
83+
newParameterInfo("something","LJvmOverloadsConstructor;","LJvmOverloadsConstructor;"),
84+
newParameterInfo("title","Ljava/lang/String;","Ljava/lang/String;"),
85+
},
86+
},
87+
},
88+
}.Assert(c);
89+
}
90+
91+
[Test]
92+
publicvoidXmlDeclaration_WithJavaType_class()
93+
{
94+
AssertXmlDeclaration(ClassFile,XmlFile);
95+
}
96+
}
97+
}
98+
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
<api
2+
api-source="class-parse">
3+
<package
4+
name=""
5+
jni-name="">
6+
<class
7+
abstract="false"
8+
deprecated="not deprecated"
9+
jni-extends="Ljava/lang/Object;"
10+
extends="java.lang.Object"
11+
extends-generic-aware="java.lang.Object"
12+
final="true"
13+
name="JvmOverloadsConstructor"
14+
jni-signature="LJvmOverloadsConstructor;"
15+
source-file-name="JvmOverloadsConstructor.kt"
16+
static="false"
17+
visibility="public">
18+
<constructor
19+
deprecated="not deprecated"
20+
final="false"
21+
name="JvmOverloadsConstructor"
22+
static="false"
23+
visibility="public"
24+
bridge="false"
25+
synthetic="false"
26+
jni-signature="(LJvmOverloadsConstructor;IILjava/lang/String;)V">
27+
<parameter
28+
name="something"
29+
type="JvmOverloadsConstructor"
30+
jni-type="LJvmOverloadsConstructor;"
31+
not-null="true" />
32+
<parameter
33+
name="id"
34+
type="int"
35+
jni-type="I" />
36+
<parameter
37+
name="imageId"
38+
type="int"
39+
jni-type="I" />
40+
<parameter
41+
name="title"
42+
type="java.lang.String"
43+
jni-type="Ljava/lang/String;"
44+
not-null="true" />
45+
</constructor>
46+
<constructor
47+
deprecated="not deprecated"
48+
final="false"
49+
name="JvmOverloadsConstructor"
50+
static="false"
51+
visibility="public"
52+
bridge="false"
53+
synthetic="false"
54+
jni-signature="(LJvmOverloadsConstructor;IILjava/lang/String;Z)V">
55+
<parameter
56+
name="something"
57+
type="JvmOverloadsConstructor"
58+
jni-type="LJvmOverloadsConstructor;"
59+
not-null="true" />
60+
<parameter
61+
name="id"
62+
type="int"
63+
jni-type="I" />
64+
<parameter
65+
name="imageId"
66+
type="int"
67+
jni-type="I" />
68+
<parameter
69+
name="title"
70+
type="java.lang.String"
71+
jni-type="Ljava/lang/String;"
72+
not-null="true" />
73+
<parameter
74+
name="useDivider"
75+
type="boolean"
76+
jni-type="Z" />
77+
</constructor>
78+
<constructor
79+
deprecated="not deprecated"
80+
final="false"
81+
name="JvmOverloadsConstructor"
82+
static="false"
83+
visibility="public"
84+
bridge="false"
85+
synthetic="true"
86+
jni-signature="(LJvmOverloadsConstructor;IILjava/lang/String;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V">
87+
<parameter
88+
name="p0"
89+
type="JvmOverloadsConstructor"
90+
jni-type="LJvmOverloadsConstructor;" />
91+
<parameter
92+
name="p1"
93+
type="int"
94+
jni-type="I" />
95+
<parameter
96+
name="p2"
97+
type="int"
98+
jni-type="I" />
99+
<parameter
100+
name="p3"
101+
type="java.lang.String"
102+
jni-type="Ljava/lang/String;" />
103+
<parameter
104+
name="p4"
105+
type="boolean"
106+
jni-type="Z" />
107+
<parameter
108+
name="p5"
109+
type="int"
110+
jni-type="I" />
111+
<parameter
112+
name="p6"
113+
type="kotlin.jvm.internal.DefaultConstructorMarker"
114+
jni-type="Lkotlin/jvm/internal/DefaultConstructorMarker;" />
115+
</constructor>
116+
<constructor
117+
deprecated="not deprecated"
118+
final="false"
119+
name="JvmOverloadsConstructor"
120+
static="false"
121+
visibility="public"
122+
bridge="false"
123+
synthetic="false"
124+
jni-signature="(LJvmOverloadsConstructor;ILjava/lang/String;)V">
125+
<parameter
126+
name="something"
127+
type="JvmOverloadsConstructor"
128+
jni-type="LJvmOverloadsConstructor;"
129+
not-null="true" />
130+
<parameter
131+
name="id"
132+
type="int"
133+
jni-type="I" />
134+
<parameter
135+
name="title"
136+
type="java.lang.String"
137+
jni-type="Ljava/lang/String;"
138+
not-null="true" />
139+
</constructor>
140+
<constructor
141+
deprecated="not deprecated"
142+
final="false"
143+
name="JvmOverloadsConstructor"
144+
static="false"
145+
visibility="public"
146+
bridge="false"
147+
synthetic="false"
148+
jni-signature="(LJvmOverloadsConstructor;Ljava/lang/String;)V">
149+
<parameter
150+
name="something"
151+
type="JvmOverloadsConstructor"
152+
jni-type="LJvmOverloadsConstructor;"
153+
not-null="true" />
154+
<parameter
155+
name="title"
156+
type="java.lang.String"
157+
jni-type="Ljava/lang/String;"
158+
not-null="true" />
159+
</constructor>
160+
</class>
161+
</package>
162+
</api>
Binary file not shown.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
classJvmOverloadsConstructor {
2+
@JvmOverloads
3+
constructor(
4+
something :JvmOverloadsConstructor,
5+
id:Int=1,
6+
imageId:Int=2,
7+
title:String,
8+
useDivider:Boolean=false
9+
) {
10+
}
11+
}

0 commit comments

Comments
 (0)