diff --git a/src/Xamarin.Android.Tools.Bytecode/AttributeInfo.cs b/src/Xamarin.Android.Tools.Bytecode/AttributeInfo.cs index 55defc599..5272504fe 100644 --- a/src/Xamarin.Android.Tools.Bytecode/AttributeInfo.cs +++ b/src/Xamarin.Android.Tools.Bytecode/AttributeInfo.cs @@ -42,6 +42,7 @@ public class AttributeInfo { public const string Exceptions = "Exceptions"; public const string InnerClasses = "InnerClasses"; public const string LocalVariableTable = "LocalVariableTable"; + public const string MethodParameters = "MethodParameters"; public const string Signature = "Signature"; public const string StackMapTable = "StackMapTable"; @@ -71,6 +72,7 @@ public string Name { { typeof (ExceptionsAttribute), Exceptions }, { typeof (InnerClassesAttribute), InnerClasses }, { typeof (LocalVariableTableAttribute), LocalVariableTable }, + { typeof (MethodParametersAttribute), MethodParameters }, { typeof (SignatureAttribute), Signature }, { typeof (StackMapTableAttribute), StackMapTable }, }; @@ -101,6 +103,7 @@ static AttributeInfo CreateAttribute (string name, ConstantPool constantPool, us case Exceptions: return new ExceptionsAttribute (constantPool, nameIndex, stream); case InnerClasses: return new InnerClassesAttribute (constantPool, nameIndex, stream); case LocalVariableTable: return new LocalVariableTableAttribute (constantPool, nameIndex, stream); + case MethodParameters: return new MethodParametersAttribute (constantPool, nameIndex, stream); case Signature: return new SignatureAttribute (constantPool, nameIndex, stream); case StackMapTable: return new StackMapTableAttribute (constantPool, nameIndex, stream); default: return new UnknownAttribute (constantPool, nameIndex, stream); @@ -389,6 +392,74 @@ public override string ToString () } } + public sealed class MethodParameterInfo { + + internal ushort nameIndex; + public MethodParameterAccessFlags AccessFlags; + + public ConstantPool ConstantPool {get; private set;} + + public MethodParameterInfo (ConstantPool constantPool) + { + ConstantPool = constantPool; + } + + public string Name { + get { + if (nameIndex == 0) + return null; + return ((ConstantPoolUtf8Item) ConstantPool [nameIndex]).Value; + } + } + + public override string ToString () + { + return string.Format ("MethodParameterInfo(Name='{0}', AccessFlags={1})", Name, AccessFlags); + } + } + + // https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.24 + public sealed class MethodParametersAttribute : AttributeInfo { + + List parameters; + + public IList ParameterInfo { + get {return parameters;} + } + + public MethodParametersAttribute (ConstantPool constantPool, ushort nameIndex, Stream stream) + : base (constantPool, nameIndex, stream) + { + var length = stream.ReadNetworkUInt32 (); + var count = stream.ReadNetworkByte (); + Debug.Assert ( + length == (checked ((count * 4) + 1)), + $"Unexpected `MethodParameters` length; expected {(count*4)+1}, got {length}!"); + parameters = new List (count); + for (int i = 0; i < count; ++i) { + var pNameIndex = stream.ReadNetworkUInt16 (); + var accessFlags = stream.ReadNetworkUInt16 (); + var p = new MethodParameterInfo (constantPool) { + nameIndex = pNameIndex, + AccessFlags = (MethodParameterAccessFlags) accessFlags, + }; + parameters.Add (p); + } + } + + public override string ToString () + { + if (parameters.Count == 0) + return "MethodParametersAttribute()"; + var sb = new StringBuilder ("MethodParametersAttribute("); + sb.Append (parameters [0]); + for (int i = 1; i < parameters.Count; ++i) + sb.Append (", ").Append (parameters [i]); + sb.Append (")"); + return sb.ToString (); + } + } + // http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.9 public sealed class SignatureAttribute : AttributeInfo { diff --git a/src/Xamarin.Android.Tools.Bytecode/Methods.cs b/src/Xamarin.Android.Tools.Bytecode/Methods.cs index a2a848405..108cba09c 100644 --- a/src/Xamarin.Android.Tools.Bytecode/Methods.cs +++ b/src/Xamarin.Android.Tools.Bytecode/Methods.cs @@ -132,6 +132,7 @@ public ParameterInfo[] GetParameters () parameters [i].Type.TypeSignature = sig.Parameters [i]; } } + UpdateParametersFromMethodParametersAttribute (parameters); return parameters; } @@ -204,6 +205,33 @@ public MethodTypeSignature GetSignature () ? new MethodTypeSignature (signature.Signature) : null; } + + void UpdateParametersFromMethodParametersAttribute (ParameterInfo[] parameters) + { + var methodParams = (MethodParametersAttribute) Attributes.SingleOrDefault (a => a.Name == AttributeInfo.MethodParameters); + if (methodParams == null) + return; + + const MethodParameterAccessFlags OuterThis = + MethodParameterAccessFlags.Mandated | MethodParameterAccessFlags.Final; + var pinfo = methodParams.ParameterInfo; + int startIndex = 0; + while (startIndex < pinfo.Count && + (pinfo [startIndex].AccessFlags & OuterThis) == OuterThis) { + startIndex++; + } + Debug.Assert ( + parameters.Length == pinfo.Count - startIndex, + $"Unexpected number of method parameters; expected {parameters.Length}, got {pinfo.Count - startIndex}"); + for (int i = 0; i < parameters.Length; ++i) { + var p = pinfo [i + startIndex]; + + parameters [i].AccessFlags = p.AccessFlags; + if (p != null) { + parameters [i].Name = p.Name; + } + } + } } public sealed class TypeInfo : IEquatable { @@ -259,6 +287,8 @@ public sealed class ParameterInfo : IEquatable { public int Position; public TypeInfo Type = new TypeInfo (); + public MethodParameterAccessFlags AccessFlags; + public ParameterInfo (string name = null, string binaryName = null, string typeSignature = null, int position = 0) { Name = name; @@ -294,7 +324,7 @@ public bool Equals (ParameterInfo other) public override string ToString () { - return string.Format ("ParameterInfo(Name={0}, Position={1}, Type={2}", Name, Position, Type); + return $"ParameterInfo(Name={Name}, Position={Position}, Type={Type}, AccessFlags={AccessFlags})"; } } @@ -313,4 +343,12 @@ public enum MethodAccessFlags { Strict = 0x0800, Synthetic = 0x1000, } + + [Flags] + public enum MethodParameterAccessFlags { + None, + Final = 0x0010, + Synthetic = 0x1000, + Mandated = 0x8000, + } } diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/IJavaInterface.xml b/src/Xamarin.Android.Tools.Bytecode/Tests/IJavaInterface.xml index bcb20e11d..def9f29ca 100644 --- a/src/Xamarin.Android.Tools.Bytecode/Tests/IJavaInterface.xml +++ b/src/Xamarin.Android.Tools.Bytecode/Tests/IJavaInterface.xml @@ -50,7 +50,7 @@ synthetic="false" jni-signature="(Ljava/lang/CharSequence;)Ljava/lang/Object;"> diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/IJavaInterfaceTests.cs b/src/Xamarin.Android.Tools.Bytecode/Tests/IJavaInterfaceTests.cs index db1408638..0d7492dc5 100644 --- a/src/Xamarin.Android.Tools.Bytecode/Tests/IJavaInterfaceTests.cs +++ b/src/Xamarin.Android.Tools.Bytecode/Tests/IJavaInterfaceTests.cs @@ -18,7 +18,7 @@ public void ClassFile_WithIJavaInterface_class () new ExpectedTypeDeclaration { MajorVersion = 0x32, MinorVersion = 0, - ConstantPoolCount = 21, + ConstantPoolCount = 23, AccessFlags = ClassAccessFlags.Interface | ClassAccessFlags.Abstract, FullName = "com/xamarin/IJavaInterface", Superclass = new TypeInfo ("java/lang/Object", "Ljava/lang/Object;"), @@ -61,7 +61,7 @@ public void ClassFile_WithIJavaInterface_class () ReturnDescriptor = "Ljava/lang/Object;", ReturnGenericDescriptor = "TTReturn;", Parameters = { - new ParameterInfo ("p0", "Ljava/lang/CharSequence;", "TTString;"), + new ParameterInfo ("value", "Ljava/lang/CharSequence;", "TTString;"), }, }, } diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/JavaEnumTests.cs b/src/Xamarin.Android.Tools.Bytecode/Tests/JavaEnumTests.cs index 27e13b03a..03f7c18b0 100644 --- a/src/Xamarin.Android.Tools.Bytecode/Tests/JavaEnumTests.cs +++ b/src/Xamarin.Android.Tools.Bytecode/Tests/JavaEnumTests.cs @@ -22,7 +22,7 @@ public void ClassFileDescription () new ExpectedTypeDeclaration { MajorVersion = 0x32, MinorVersion = 0, - ConstantPoolCount = 50, + ConstantPoolCount = 53, AccessFlags = ClassAccessFlags.Final | ClassAccessFlags.Super | ClassAccessFlags.Enum, FullName = "com/xamarin/JavaEnum", Superclass = new TypeInfo ("java/lang/Enum", "Ljava/lang/Enum;"), @@ -62,8 +62,8 @@ public void ClassFileDescription () AccessFlags = MethodAccessFlags.Private, ReturnDescriptor = "V", Parameters = { - new ParameterInfo ("p0", "Ljava/lang/String;", "Ljava/lang/String;"), - new ParameterInfo ("p1", "I", "I"), + new ParameterInfo ("$enum$name", "Ljava/lang/String;", "Ljava/lang/String;"), + new ParameterInfo ("$enum$ordinal", "I", "I"), }, }, new ExpectedMethodDeclaration { diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/JavaType$RNC$RPNC.xml b/src/Xamarin.Android.Tools.Bytecode/Tests/JavaType$RNC$RPNC.xml index 076690b5b..e9c4afbfc 100644 --- a/src/Xamarin.Android.Tools.Bytecode/Tests/JavaType$RNC$RPNC.xml +++ b/src/Xamarin.Android.Tools.Bytecode/Tests/JavaType$RNC$RPNC.xml @@ -68,7 +68,7 @@ synthetic="false" jni-signature="(Ljava/lang/Object;)Ljava/lang/Object;"> diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/JavaType$RNC.xml b/src/Xamarin.Android.Tools.Bytecode/Tests/JavaType$RNC.xml index 393ec116f..9b175914d 100644 --- a/src/Xamarin.Android.Tools.Bytecode/Tests/JavaType$RNC.xml +++ b/src/Xamarin.Android.Tools.Bytecode/Tests/JavaType$RNC.xml @@ -64,7 +64,7 @@ synthetic="false" jni-signature="(Ljava/lang/Object;)Ljava/lang/Object;"> diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/JavaType.RNC.RPNCTests.cs b/src/Xamarin.Android.Tools.Bytecode/Tests/JavaType.RNC.RPNCTests.cs index ea3e0fca9..85119703f 100644 --- a/src/Xamarin.Android.Tools.Bytecode/Tests/JavaType.RNC.RPNCTests.cs +++ b/src/Xamarin.Android.Tools.Bytecode/Tests/JavaType.RNC.RPNCTests.cs @@ -18,7 +18,7 @@ public void ClassFileDescription () new ExpectedTypeDeclaration { MajorVersion = 0x32, MinorVersion = 0, - ConstantPoolCount = 44, + ConstantPoolCount = 46, AccessFlags = ClassAccessFlags.Public | ClassAccessFlags.Super | ClassAccessFlags.Abstract, FullName = "com/xamarin/JavaType$RNC$RPNC", Superclass = new TypeInfo ("java/lang/Object", "Ljava/lang/Object;"), @@ -71,7 +71,7 @@ public void ClassFileDescription () ReturnDescriptor = "Ljava/lang/Object;", ReturnGenericDescriptor = "TE3;", Parameters = { - new ParameterInfo ("p0", "Ljava/lang/Object;", "TE2;"), + new ParameterInfo ("value", "Ljava/lang/Object;", "TE2;"), }, }, } diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/JavaType.RNCTests.cs b/src/Xamarin.Android.Tools.Bytecode/Tests/JavaType.RNCTests.cs index 775e46482..d25ddfcdc 100644 --- a/src/Xamarin.Android.Tools.Bytecode/Tests/JavaType.RNCTests.cs +++ b/src/Xamarin.Android.Tools.Bytecode/Tests/JavaType.RNCTests.cs @@ -18,7 +18,7 @@ public void ClassFileDescription () new ExpectedTypeDeclaration { MajorVersion = 0x32, MinorVersion = 0, - ConstantPoolCount = 42, + ConstantPoolCount = 44, AccessFlags = ClassAccessFlags.Public | ClassAccessFlags.Super | ClassAccessFlags.Abstract, FullName = "com/xamarin/JavaType$RNC", Superclass = new TypeInfo ("java/lang/Object", "Ljava/lang/Object;"), @@ -70,7 +70,7 @@ public void ClassFileDescription () ReturnDescriptor = "Ljava/lang/Object;", ReturnGenericDescriptor = "TE2;", Parameters = { - new ParameterInfo ("p0", "Ljava/lang/Object;", "TE;"), + new ParameterInfo ("value", "Ljava/lang/Object;", "TE;"), }, }, } diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/JavaType.xml b/src/Xamarin.Android.Tools.Bytecode/Tests/JavaType.xml index 832ad872a..3e34f096e 100644 --- a/src/Xamarin.Android.Tools.Bytecode/Tests/JavaType.xml +++ b/src/Xamarin.Android.Tools.Bytecode/Tests/JavaType.xml @@ -110,7 +110,7 @@ synthetic="true" jni-signature="(Ljava/lang/Object;)I"> @@ -162,7 +162,7 @@ synthetic="true" jni-signature="(Ljava/lang/CharSequence;)Ljava/lang/Object;"> diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/JavaTypeTests.cs b/src/Xamarin.Android.Tools.Bytecode/Tests/JavaTypeTests.cs index c921210b9..234825b31 100644 --- a/src/Xamarin.Android.Tools.Bytecode/Tests/JavaTypeTests.cs +++ b/src/Xamarin.Android.Tools.Bytecode/Tests/JavaTypeTests.cs @@ -20,7 +20,7 @@ public void ClassFile_WithJavaType_class () new ExpectedTypeDeclaration { MajorVersion = 0x32, MinorVersion = 0, - ConstantPoolCount = 183, + ConstantPoolCount = 184, AccessFlags = ClassAccessFlags.Public | ClassAccessFlags.Super, FullName = "com/xamarin/JavaType", Superclass = new TypeInfo ("java/lang/Object", "Ljava/lang/Object;"), @@ -132,7 +132,7 @@ public void ClassFile_WithJavaType_class () Name = "STATIC_FINAL_STRING", Descriptor = "Ljava/lang/String;", AccessFlags = FieldAccessFlags.Public | FieldAccessFlags.Static | FieldAccessFlags.Final, - ConstantValue = "String(stringIndex=175 Utf8=\"Hello, \\\"embedded\0Nulls\" and \ud83d\udca9!\")", + ConstantValue = "String(stringIndex=176 Utf8=\"Hello, \\\"embedded\0Nulls\" and \ud83d\udca9!\")", }, new ExpectedFieldDeclaration { Name = "STATIC_FINAL_BOOL_FALSE", @@ -309,7 +309,7 @@ public void ClassFile_WithJavaType_class () AccessFlags = MethodAccessFlags.Public | MethodAccessFlags.Bridge | MethodAccessFlags.Synthetic, ReturnDescriptor = "I", Parameters = { - new ParameterInfo ("p0", "Ljava/lang/Object;", "Ljava/lang/Object;"), + new ParameterInfo ("value", "Ljava/lang/Object;", "Ljava/lang/Object;"), }, }, new ExpectedMethodDeclaration { @@ -317,7 +317,7 @@ public void ClassFile_WithJavaType_class () AccessFlags = MethodAccessFlags.Public | MethodAccessFlags.Bridge | MethodAccessFlags.Synthetic, ReturnDescriptor = "Ljava/lang/Object;", Parameters = { - new ParameterInfo ("p0", "Ljava/lang/CharSequence;", "Ljava/lang/CharSequence;"), + new ParameterInfo ("value", "Ljava/lang/CharSequence;", "Ljava/lang/CharSequence;"), }, }, new ExpectedMethodDeclaration { diff --git a/src/Xamarin.Android.Tools.Bytecode/Tests/Xamarin.Android.Tools.Bytecode-Tests.csproj b/src/Xamarin.Android.Tools.Bytecode/Tests/Xamarin.Android.Tools.Bytecode-Tests.csproj index efa111cc3..c66587660 100644 --- a/src/Xamarin.Android.Tools.Bytecode/Tests/Xamarin.Android.Tools.Bytecode-Tests.csproj +++ b/src/Xamarin.Android.Tools.Bytecode/Tests/Xamarin.Android.Tools.Bytecode-Tests.csproj @@ -56,7 +56,13 @@ - + + @@ -67,7 +73,8 @@ - + +