Skip to content

Commit 4273e5c

Browse files
authored
[Xamarin.Android.Tools.Bytecode] MethodParameters Attribute parsing (#107)
Fixes: #106 Java 8 adds support for a `javac -parameters` option, which adds a `MethodParameters` attribute to the `.class` file. The `MethodParameters` attribute blob contains *reliable* parameter names, including for abstract methods and interface methods, unlike existing `LocalVariableTable`, which only exists for `javac -g` builds, and only for methods with *bodies*, i.e. *not* abstract methods and interface methods. Add support for parsing the `MethodParameters` attribute blob.
1 parent c73302c commit 4273e5c

12 files changed

+137
-21
lines changed

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

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public class AttributeInfo {
4242
public const string Exceptions = "Exceptions";
4343
public const string InnerClasses = "InnerClasses";
4444
public const string LocalVariableTable = "LocalVariableTable";
45+
public const string MethodParameters = "MethodParameters";
4546
public const string Signature = "Signature";
4647
public const string StackMapTable = "StackMapTable";
4748

@@ -71,6 +72,7 @@ public string Name {
7172
{ typeof (ExceptionsAttribute), Exceptions },
7273
{ typeof (InnerClassesAttribute), InnerClasses },
7374
{ typeof (LocalVariableTableAttribute), LocalVariableTable },
75+
{ typeof (MethodParametersAttribute), MethodParameters },
7476
{ typeof (SignatureAttribute), Signature },
7577
{ typeof (StackMapTableAttribute), StackMapTable },
7678
};
@@ -101,6 +103,7 @@ static AttributeInfo CreateAttribute (string name, ConstantPool constantPool, us
101103
case Exceptions: return new ExceptionsAttribute (constantPool, nameIndex, stream);
102104
case InnerClasses: return new InnerClassesAttribute (constantPool, nameIndex, stream);
103105
case LocalVariableTable: return new LocalVariableTableAttribute (constantPool, nameIndex, stream);
106+
case MethodParameters: return new MethodParametersAttribute (constantPool, nameIndex, stream);
104107
case Signature: return new SignatureAttribute (constantPool, nameIndex, stream);
105108
case StackMapTable: return new StackMapTableAttribute (constantPool, nameIndex, stream);
106109
default: return new UnknownAttribute (constantPool, nameIndex, stream);
@@ -389,6 +392,74 @@ public override string ToString ()
389392
}
390393
}
391394

395+
public sealed class MethodParameterInfo {
396+
397+
internal ushort nameIndex;
398+
public MethodParameterAccessFlags AccessFlags;
399+
400+
public ConstantPool ConstantPool {get; private set;}
401+
402+
public MethodParameterInfo (ConstantPool constantPool)
403+
{
404+
ConstantPool = constantPool;
405+
}
406+
407+
public string Name {
408+
get {
409+
if (nameIndex == 0)
410+
return null;
411+
return ((ConstantPoolUtf8Item) ConstantPool [nameIndex]).Value;
412+
}
413+
}
414+
415+
public override string ToString ()
416+
{
417+
return string.Format ("MethodParameterInfo(Name='{0}', AccessFlags={1})", Name, AccessFlags);
418+
}
419+
}
420+
421+
// https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.24
422+
public sealed class MethodParametersAttribute : AttributeInfo {
423+
424+
List<MethodParameterInfo> parameters;
425+
426+
public IList<MethodParameterInfo> ParameterInfo {
427+
get {return parameters;}
428+
}
429+
430+
public MethodParametersAttribute (ConstantPool constantPool, ushort nameIndex, Stream stream)
431+
: base (constantPool, nameIndex, stream)
432+
{
433+
var length = stream.ReadNetworkUInt32 ();
434+
var count = stream.ReadNetworkByte ();
435+
Debug.Assert (
436+
length == (checked ((count * 4) + 1)),
437+
$"Unexpected `MethodParameters` length; expected {(count*4)+1}, got {length}!");
438+
parameters = new List<MethodParameterInfo> (count);
439+
for (int i = 0; i < count; ++i) {
440+
var pNameIndex = stream.ReadNetworkUInt16 ();
441+
var accessFlags = stream.ReadNetworkUInt16 ();
442+
var p = new MethodParameterInfo (constantPool) {
443+
nameIndex = pNameIndex,
444+
AccessFlags = (MethodParameterAccessFlags) accessFlags,
445+
};
446+
parameters.Add (p);
447+
}
448+
}
449+
450+
public override string ToString ()
451+
{
452+
if (parameters.Count == 0)
453+
return "MethodParametersAttribute()";
454+
var sb = new StringBuilder ("MethodParametersAttribute(");
455+
sb.Append (parameters [0]);
456+
for (int i = 1; i < parameters.Count; ++i)
457+
sb.Append (", ").Append (parameters [i]);
458+
sb.Append (")");
459+
return sb.ToString ();
460+
}
461+
}
462+
392463
// http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.9
393464
public sealed class SignatureAttribute : AttributeInfo {
394465

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

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ public ParameterInfo[] GetParameters ()
132132
parameters [i].Type.TypeSignature = sig.Parameters [i];
133133
}
134134
}
135+
UpdateParametersFromMethodParametersAttribute (parameters);
135136
return parameters;
136137
}
137138

@@ -204,6 +205,33 @@ public MethodTypeSignature GetSignature ()
204205
? new MethodTypeSignature (signature.Signature)
205206
: null;
206207
}
208+
209+
void UpdateParametersFromMethodParametersAttribute (ParameterInfo[] parameters)
210+
{
211+
var methodParams = (MethodParametersAttribute) Attributes.SingleOrDefault (a => a.Name == AttributeInfo.MethodParameters);
212+
if (methodParams == null)
213+
return;
214+
215+
const MethodParameterAccessFlags OuterThis =
216+
MethodParameterAccessFlags.Mandated | MethodParameterAccessFlags.Final;
217+
var pinfo = methodParams.ParameterInfo;
218+
int startIndex = 0;
219+
while (startIndex < pinfo.Count &&
220+
(pinfo [startIndex].AccessFlags & OuterThis) == OuterThis) {
221+
startIndex++;
222+
}
223+
Debug.Assert (
224+
parameters.Length == pinfo.Count - startIndex,
225+
$"Unexpected number of method parameters; expected {parameters.Length}, got {pinfo.Count - startIndex}");
226+
for (int i = 0; i < parameters.Length; ++i) {
227+
var p = pinfo [i + startIndex];
228+
229+
parameters [i].AccessFlags = p.AccessFlags;
230+
if (p != null) {
231+
parameters [i].Name = p.Name;
232+
}
233+
}
234+
}
207235
}
208236

209237
public sealed class TypeInfo : IEquatable<TypeInfo> {
@@ -259,6 +287,8 @@ public sealed class ParameterInfo : IEquatable<ParameterInfo> {
259287
public int Position;
260288
public TypeInfo Type = new TypeInfo ();
261289

290+
public MethodParameterAccessFlags AccessFlags;
291+
262292
public ParameterInfo (string name = null, string binaryName = null, string typeSignature = null, int position = 0)
263293
{
264294
Name = name;
@@ -294,7 +324,7 @@ public bool Equals (ParameterInfo other)
294324

295325
public override string ToString ()
296326
{
297-
return string.Format ("ParameterInfo(Name={0}, Position={1}, Type={2}", Name, Position, Type);
327+
return $"ParameterInfo(Name={Name}, Position={Position}, Type={Type}, AccessFlags={AccessFlags})";
298328
}
299329
}
300330

@@ -313,4 +343,12 @@ public enum MethodAccessFlags {
313343
Strict = 0x0800,
314344
Synthetic = 0x1000,
315345
}
346+
347+
[Flags]
348+
public enum MethodParameterAccessFlags {
349+
None,
350+
Final = 0x0010,
351+
Synthetic = 0x1000,
352+
Mandated = 0x8000,
353+
}
316354
}

src/Xamarin.Android.Tools.Bytecode/Tests/IJavaInterface.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
synthetic="false"
5151
jni-signature="(Ljava/lang/CharSequence;)Ljava/lang/Object;">
5252
<parameter
53-
name="p0"
53+
name="value"
5454
type="TString"
5555
jni-type="TTString;" />
5656
</method>

src/Xamarin.Android.Tools.Bytecode/Tests/IJavaInterfaceTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public void ClassFile_WithIJavaInterface_class ()
1818
new ExpectedTypeDeclaration {
1919
MajorVersion = 0x32,
2020
MinorVersion = 0,
21-
ConstantPoolCount = 21,
21+
ConstantPoolCount = 23,
2222
AccessFlags = ClassAccessFlags.Interface | ClassAccessFlags.Abstract,
2323
FullName = "com/xamarin/IJavaInterface",
2424
Superclass = new TypeInfo ("java/lang/Object", "Ljava/lang/Object;"),
@@ -61,7 +61,7 @@ public void ClassFile_WithIJavaInterface_class ()
6161
ReturnDescriptor = "Ljava/lang/Object;",
6262
ReturnGenericDescriptor = "TTReturn;",
6363
Parameters = {
64-
new ParameterInfo ("p0", "Ljava/lang/CharSequence;", "TTString;"),
64+
new ParameterInfo ("value", "Ljava/lang/CharSequence;", "TTString;"),
6565
},
6666
},
6767
}

src/Xamarin.Android.Tools.Bytecode/Tests/JavaEnumTests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public void ClassFileDescription ()
2222
new ExpectedTypeDeclaration {
2323
MajorVersion = 0x32,
2424
MinorVersion = 0,
25-
ConstantPoolCount = 50,
25+
ConstantPoolCount = 53,
2626
AccessFlags = ClassAccessFlags.Final | ClassAccessFlags.Super | ClassAccessFlags.Enum,
2727
FullName = "com/xamarin/JavaEnum",
2828
Superclass = new TypeInfo ("java/lang/Enum", "Ljava/lang/Enum<Lcom/xamarin/JavaEnum;>;"),
@@ -62,8 +62,8 @@ public void ClassFileDescription ()
6262
AccessFlags = MethodAccessFlags.Private,
6363
ReturnDescriptor = "V",
6464
Parameters = {
65-
new ParameterInfo ("p0", "Ljava/lang/String;", "Ljava/lang/String;"),
66-
new ParameterInfo ("p1", "I", "I"),
65+
new ParameterInfo ("$enum$name", "Ljava/lang/String;", "Ljava/lang/String;"),
66+
new ParameterInfo ("$enum$ordinal", "I", "I"),
6767
},
6868
},
6969
new ExpectedMethodDeclaration {

src/Xamarin.Android.Tools.Bytecode/Tests/JavaType$RNC$RPNC.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
synthetic="false"
6969
jni-signature="(Ljava/lang/Object;)Ljava/lang/Object;">
7070
<parameter
71-
name="p0"
71+
name="value"
7272
type="E2"
7373
jni-type="TE2;" />
7474
</method>

src/Xamarin.Android.Tools.Bytecode/Tests/JavaType$RNC.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
synthetic="false"
6565
jni-signature="(Ljava/lang/Object;)Ljava/lang/Object;">
6666
<parameter
67-
name="p0"
67+
name="value"
6868
type="E"
6969
jni-type="TE;" />
7070
</method>

src/Xamarin.Android.Tools.Bytecode/Tests/JavaType.RNC.RPNCTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public void ClassFileDescription ()
1818
new ExpectedTypeDeclaration {
1919
MajorVersion = 0x32,
2020
MinorVersion = 0,
21-
ConstantPoolCount = 44,
21+
ConstantPoolCount = 46,
2222
AccessFlags = ClassAccessFlags.Public | ClassAccessFlags.Super | ClassAccessFlags.Abstract,
2323
FullName = "com/xamarin/JavaType$RNC$RPNC",
2424
Superclass = new TypeInfo ("java/lang/Object", "Ljava/lang/Object;"),
@@ -71,7 +71,7 @@ public void ClassFileDescription ()
7171
ReturnDescriptor = "Ljava/lang/Object;",
7272
ReturnGenericDescriptor = "TE3;",
7373
Parameters = {
74-
new ParameterInfo ("p0", "Ljava/lang/Object;", "TE2;"),
74+
new ParameterInfo ("value", "Ljava/lang/Object;", "TE2;"),
7575
},
7676
},
7777
}

src/Xamarin.Android.Tools.Bytecode/Tests/JavaType.RNCTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public void ClassFileDescription ()
1818
new ExpectedTypeDeclaration {
1919
MajorVersion = 0x32,
2020
MinorVersion = 0,
21-
ConstantPoolCount = 42,
21+
ConstantPoolCount = 44,
2222
AccessFlags = ClassAccessFlags.Public | ClassAccessFlags.Super | ClassAccessFlags.Abstract,
2323
FullName = "com/xamarin/JavaType$RNC",
2424
Superclass = new TypeInfo ("java/lang/Object", "Ljava/lang/Object;"),
@@ -70,7 +70,7 @@ public void ClassFileDescription ()
7070
ReturnDescriptor = "Ljava/lang/Object;",
7171
ReturnGenericDescriptor = "TE2;",
7272
Parameters = {
73-
new ParameterInfo ("p0", "Ljava/lang/Object;", "TE;"),
73+
new ParameterInfo ("value", "Ljava/lang/Object;", "TE;"),
7474
},
7575
},
7676
}

src/Xamarin.Android.Tools.Bytecode/Tests/JavaType.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@
110110
synthetic="true"
111111
jni-signature="(Ljava/lang/Object;)I">
112112
<parameter
113-
name="p0"
113+
name="value"
114114
type="java.lang.Object"
115115
jni-type="Ljava/lang/Object;" />
116116
</method>
@@ -162,7 +162,7 @@
162162
synthetic="true"
163163
jni-signature="(Ljava/lang/CharSequence;)Ljava/lang/Object;">
164164
<parameter
165-
name="p0"
165+
name="value"
166166
type="java.lang.CharSequence"
167167
jni-type="Ljava/lang/CharSequence;" />
168168
</method>

src/Xamarin.Android.Tools.Bytecode/Tests/JavaTypeTests.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public void ClassFile_WithJavaType_class ()
2020
new ExpectedTypeDeclaration {
2121
MajorVersion = 0x32,
2222
MinorVersion = 0,
23-
ConstantPoolCount = 183,
23+
ConstantPoolCount = 184,
2424
AccessFlags = ClassAccessFlags.Public | ClassAccessFlags.Super,
2525
FullName = "com/xamarin/JavaType",
2626
Superclass = new TypeInfo ("java/lang/Object", "Ljava/lang/Object;"),
@@ -132,7 +132,7 @@ public void ClassFile_WithJavaType_class ()
132132
Name = "STATIC_FINAL_STRING",
133133
Descriptor = "Ljava/lang/String;",
134134
AccessFlags = FieldAccessFlags.Public | FieldAccessFlags.Static | FieldAccessFlags.Final,
135-
ConstantValue = "String(stringIndex=175 Utf8=\"Hello, \\\"embedded\0Nulls\" and \ud83d\udca9!\")",
135+
ConstantValue = "String(stringIndex=176 Utf8=\"Hello, \\\"embedded\0Nulls\" and \ud83d\udca9!\")",
136136
},
137137
new ExpectedFieldDeclaration {
138138
Name = "STATIC_FINAL_BOOL_FALSE",
@@ -309,15 +309,15 @@ public void ClassFile_WithJavaType_class ()
309309
AccessFlags = MethodAccessFlags.Public | MethodAccessFlags.Bridge | MethodAccessFlags.Synthetic,
310310
ReturnDescriptor = "I",
311311
Parameters = {
312-
new ParameterInfo ("p0", "Ljava/lang/Object;", "Ljava/lang/Object;"),
312+
new ParameterInfo ("value", "Ljava/lang/Object;", "Ljava/lang/Object;"),
313313
},
314314
},
315315
new ExpectedMethodDeclaration {
316316
Name = "func",
317317
AccessFlags = MethodAccessFlags.Public | MethodAccessFlags.Bridge | MethodAccessFlags.Synthetic,
318318
ReturnDescriptor = "Ljava/lang/Object;",
319319
Parameters = {
320-
new ParameterInfo ("p0", "Ljava/lang/CharSequence;", "Ljava/lang/CharSequence;"),
320+
new ParameterInfo ("value", "Ljava/lang/CharSequence;", "Ljava/lang/CharSequence;"),
321321
},
322322
},
323323
new ExpectedMethodDeclaration {

src/Xamarin.Android.Tools.Bytecode/Tests/Xamarin.Android.Tools.Bytecode-Tests.csproj

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,13 @@
5656
<Compile Include="ParameterFixupTests.cs" />
5757
</ItemGroup>
5858
<ItemGroup>
59-
<TestJar Include="java\**\*.java" />
59+
<TestJar
60+
Include="java\**\*.java"
61+
Exclude="java\java\util\Collection.java"
62+
/>
63+
<TestJarNoParameters
64+
Include="java\java\util\Collection.java"
65+
/>
6066
</ItemGroup>
6167
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
6268
<PropertyGroup>
@@ -67,7 +73,8 @@
6773
</PropertyGroup>
6874
<Target Name="BuildClasses" Inputs="@(TestJar)" Outputs="@(TestJar-&gt;'$(IntermediateOutputPath)classes\%(RecursiveDir)%(Filename).class')">
6975
<MakeDir Directories="$(IntermediateOutputPath)classes" />
70-
<Exec Command="&quot;javac&quot; -source 1.5 -target 1.6 -g -d &quot;$(IntermediateOutputPath)classes&quot; @(TestJar -&gt; '%(Identity)', ' ')" />
76+
<Exec Command="&quot;javac&quot; -parameters -source 1.5 -target 1.6 -g -d &quot;$(IntermediateOutputPath)classes&quot; @(TestJar->'%(Identity)', ' ')" />
77+
<Exec Command="&quot;javac&quot; -source 1.5 -target 1.6 -g -d &quot;$(IntermediateOutputPath)classes&quot; @(TestJarNoParameters->'%(Identity)', ' ')" />
7178
</Target>
7279
<ItemGroup>
7380
<ProjectReference Include="..\Xamarin.Android.Tools.Bytecode.csproj">

0 commit comments

Comments
 (0)