diff --git a/src/Xamarin.Android.Tools.Bytecode/AttributeInfo.cs b/src/Xamarin.Android.Tools.Bytecode/AttributeInfo.cs index 62ae790e9..fc7577af0 100644 --- a/src/Xamarin.Android.Tools.Bytecode/AttributeInfo.cs +++ b/src/Xamarin.Android.Tools.Bytecode/AttributeInfo.cs @@ -360,6 +360,8 @@ public string InnerName { } } + public string OuterClassName => OuterClass?.Name?.Value; + public override string ToString () { return string.Format ("InnerClass(InnerClass='{0}', OuterClass='{1}', InnerName='{2}', InnerClassAccessFlags={3})", diff --git a/src/Xamarin.Android.Tools.Bytecode/ClassPath.cs b/src/Xamarin.Android.Tools.Bytecode/ClassPath.cs index cec9b9041..757190789 100644 --- a/src/Xamarin.Android.Tools.Bytecode/ClassPath.cs +++ b/src/Xamarin.Android.Tools.Bytecode/ClassPath.cs @@ -41,14 +41,22 @@ public ClassPath (string path = null) Load (path); } - public void Load (string jarFile) + public void Load (string filePath) { - if (!IsJarFile (jarFile)) - throw new ArgumentException ("'jarFile' is not a valid .jar file.", "jarFile"); - - using (var jarStream = File.OpenRead (jarFile)) { - Load (jarStream); + if (IsJmodFile (filePath)) { + using (var source = File.OpenRead (filePath)) { + var slice = new PartialStream (source, 4); + Load (slice); + } + return; + } + if (IsJarFile (filePath)) { + using (var jarStream = File.OpenRead (filePath)) { + Load (jarStream); + } + return; } + throw new ArgumentException ($"`{filePath}` is not a supported file format.", nameof (filePath)); } public void Load (Stream jarStream, bool leaveOpen = false) @@ -113,6 +121,23 @@ public static bool IsJarFile (string jarFile) } } + public static bool IsJmodFile (string jmodFile) + { + if (jmodFile == null) + throw new ArgumentNullException (nameof (jmodFile)); + try { + var f = File.OpenRead (jmodFile); + var h = new byte[4]; + if (f.Read (h, 0, h.Length) != 4) { + return false; + } + return h[0] == 0x4a && h[1] == 0x4d && h[2] == 0x01 && h[3] == 0x00; + } + catch (Exception) { + return false; + } + } + XAttribute GetApiSource () { if (string.IsNullOrEmpty (ApiSource)) diff --git a/src/Xamarin.Android.Tools.Bytecode/Log.cs b/src/Xamarin.Android.Tools.Bytecode/Log.cs index 7cdf2ca86..bd7c70a2b 100644 --- a/src/Xamarin.Android.Tools.Bytecode/Log.cs +++ b/src/Xamarin.Android.Tools.Bytecode/Log.cs @@ -15,6 +15,8 @@ public static void Warning (int verbosity, string format, params object[] args) log (TraceLevel.Warning, verbosity, format, args); } + public static void Warning (int verbosity, string message) => Warning (verbosity, "{0}", message); + public static void Error (string format, params object[] args) { var log = OnLog; @@ -23,6 +25,8 @@ public static void Error (string format, params object[] args) log (TraceLevel.Error, 0, format, args); } + public static void Error (string message) => Error ("{0}", message); + public static void Message (string format, params object[] args) { var log = OnLog; @@ -31,6 +35,8 @@ public static void Message (string format, params object[] args) log (TraceLevel.Info, 0, format, args); } + public static void Message (string message) => Message ("{0}", message); + public static void Debug (string format, params object[] args) { var log = OnLog; @@ -38,6 +44,8 @@ public static void Debug (string format, params object[] args) return; log (TraceLevel.Verbose, 0, format, args); } + + public static void Debug (string message) => Debug ("{0}", message); } } diff --git a/src/Xamarin.Android.Tools.Bytecode/Methods.cs b/src/Xamarin.Android.Tools.Bytecode/Methods.cs index dc5d46638..13cd56b18 100644 --- a/src/Xamarin.Android.Tools.Bytecode/Methods.cs +++ b/src/Xamarin.Android.Tools.Bytecode/Methods.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.IO; using System.Linq; +using System.Text; namespace Xamarin.Android.Tools.Bytecode { @@ -77,6 +78,8 @@ public TypeInfo ReturnType { } } + bool IsEnumCtor => IsConstructor && DeclaringType.IsEnum; + ParameterInfo[] parameters = null; public ParameterInfo[] GetParameters () @@ -85,73 +88,24 @@ public ParameterInfo[] GetParameters () return parameters; int _; parameters = GetParametersFromDescriptor (out _).ToArray (); - var locals = GetLocalVariables (); - var enumCtor = IsConstructor && DeclaringType.IsEnum; - if (locals != null) { - var names = locals.LocalVariables.Where (p => p.StartPC == 0).ToList (); - int start = 0; - if (names.Count != parameters.Length && - !AccessFlags.HasFlag (MethodAccessFlags.Static) && - names.Count > start && - names [start].Descriptor == DeclaringType.FullJniName) { - start++; // skip `this` parameter - } - if (!DeclaringType.IsStatic && - names.Count > start && - (parameters.Length == 0 || parameters [0].Type.BinaryName != names [start].Descriptor)) { - start++; // JDK 8? - } - if (!AccessFlags.HasFlag (MethodAccessFlags.Synthetic) && - ((names.Count - start) != parameters.Length) && - !enumCtor) { - Log.Debug ("class-parse: method {0}.{1}{2}: " + - "Local variables array has {3} entries ('{4}'); descriptor has {5} entries!", - DeclaringType.ThisClass.Name.Value, Name, Descriptor, - names.Count - start, - Attributes.Get().Attributes.Get (), - parameters.Length); - } - int max = Math.Min (parameters.Length, names.Count - start); - for (int i = 0; i < max; ++i) { - parameters [i].Name = names [start+i].Name; - if (parameters [i].Type.BinaryName != names [start + i].Descriptor) { - Log.Debug ("class-parse: method {0}.{1}{2}: " + - "Local variable type descriptor mismatch! Got '{3}'; expected '{4}'.", - DeclaringType.ThisClass.Name.Value, Name, Descriptor, - parameters [i].Type.BinaryName, - names [start + i].Descriptor); - } - } - } - var sig = GetSignature (); - if (sig != null) { - if ((sig.Parameters.Count != parameters.Length) && !enumCtor) { - Log.Debug ("class-parse: method {0}.{1}{2}: " + - "Signature ('{3}') has {4} entries; Descriptor '{5}' has {6} entries!", - DeclaringType.ThisClass.Name.Value, Name, Descriptor, - Attributes.Get(), - sig.Parameters.Count, - Descriptor, - parameters.Length); - } - int max = Math.Min (parameters.Length, sig.Parameters.Count); - for (int i = 0; i < max; ++i) { - parameters [i].Type.TypeSignature = sig.Parameters [i]; - } - } + UpdateParametersFromLocalVariables (parameters); + UpdateParametersFromSignature (parameters); UpdateParametersFromMethodParametersAttribute (parameters); return parameters; } - - LocalVariableTableAttribute GetLocalVariables () + static IEnumerable ExtractTypesFromSignature (string signature) { - var code = Attributes.Get (); - if (code == null) - return null; - var locals = (LocalVariableTableAttribute) code.Attributes.FirstOrDefault (a => a.Name == "LocalVariableTable"); - return locals; - } + if (signature == null || signature.Length < "()V".Length) + throw new InvalidOperationException (string.Format ("Invalid method descriptor '{0}'.", signature)); + if (signature [0] != '(') + throw new InvalidOperationException (string.Format ("Invalid method descriptor '{0}'; expected '(' at index 0.", signature)); + + int index = 1; + while (index < signature.Length && signature [index] != ')') { + yield return Signature.ExtractType (signature, ref index); + } + } List GetParametersFromDescriptor (out int endParams) { var signature = Descriptor; @@ -162,14 +116,36 @@ List GetParametersFromDescriptor (out int endParams) int index = 1; - // non-static inner classes have a "hidden" parameter. Skip it. - if (IsConstructor && DeclaringType.InnerClass != null && !DeclaringType.IsStatic && signature [index] != ')') - Signature.ExtractType (signature, ref index); - int c = 0; + var first = true; var ps = new List (); while (index < signature.Length && signature [index] != ')') { var type = Signature.ExtractType (signature, ref index); + + if (first) { + first = false; + if (IsConstructor && + !DeclaringType.IsStatic && + DeclaringType.TryGetEnclosingMethodInfo (out var declaringClass, out var _, out var _) && + type == "L" + declaringClass + ";") { + continue; + } + if (IsConstructor && + !DeclaringType.IsStatic && + DeclaringType.InnerClass?.OuterClassName != null && + type == "L" + DeclaringType.InnerClass.OuterClassName + ";") { + continue; + } + } + + if (first && + IsConstructor && + DeclaringType.InnerClass?.OuterClassName != null && + type == "L" + DeclaringType.InnerClass.OuterClassName + ";") { + first = false; + continue; + } + var p = new ParameterInfo { Position = c, Name = "p" + (c++), @@ -182,6 +158,187 @@ List GetParametersFromDescriptor (out int endParams) return ps; } + void UpdateParametersFromLocalVariables (ParameterInfo[] parameters) + { + var locals = GetLocalVariables (); + if (locals == null) + return; + + var names = locals.LocalVariables.Where (p => p.StartPC == 0).ToList (); + int namesStart = 0; + if (!AccessFlags.HasFlag (MethodAccessFlags.Static) && + names.Count > namesStart && + names [namesStart].Descriptor == DeclaringType.FullJniName) { + namesStart++; // skip `this` parameter + } + if (!DeclaringType.IsStatic && + IsConstructor && + names.Count > namesStart && + DeclaringType.InnerClass != null && DeclaringType.InnerClass.OuterClassName != null && + names [namesStart].Descriptor == "L" + DeclaringType.InnerClass.OuterClassName + ";") { + namesStart++; // "outer `this`", for non-static inner classes + } + if (!DeclaringType.IsStatic && + IsConstructor && + names.Count > namesStart && + DeclaringType.TryGetEnclosingMethodInfo (out var declaringClass, out var _, out var _) && + names [namesStart].Descriptor == "L" + declaringClass + ";") { + namesStart++; // "outer `this`", for non-static inner classes + } + + // For JvmOverloadsConstructor..(LJvmOverloadsConstructor;IILjava/lang/String;)V + if (namesStart > 0 && + names.Count > namesStart && + parameters.Length > 0 && + names [namesStart].Descriptor != parameters [0].Type.BinaryName && + names [namesStart-1].Descriptor == parameters [0].Type.BinaryName) { + namesStart--; + } + + int parametersCount = GetDeclaredParametersCount (parameters); + CheckDescriptorVariablesToLocalVariables (parameters, parametersCount, names, namesStart); + + int max = Math.Min (parametersCount, names.Count - namesStart); + for (int i = 0; i < max; ++i) { + parameters [i].Name = names [namesStart+i].Name; + CheckLocalVariableTypeToDescriptorType (i, parameters, names, namesStart); + } + } + + LocalVariableTableAttribute GetLocalVariables () + { + var code = Attributes.Get (); + if (code == null) + return null; + var locals = (LocalVariableTableAttribute) code.Attributes.FirstOrDefault (a => a.Name == "LocalVariableTable"); + return locals; + } + + int GetDeclaredParametersCount (ParameterInfo[] parameters) + { + // Consider the `MyStringList` inner class declared within `JavaType.staticActionWithGenerics()`. + // The inner class acts as a closure over method parameters; *some* parameters are stored as + // fields within the class, and provided as "trailing parameters" to the constructor. + // + // The problem is that I can't see a "clean" way to tell which parameters are "closure trailing parameters" + // vs. "real" parameters. + // + // Thus, a hacky way: a "closure trailing parameter" is a type: + // 1. Declared in the enclosing method, which is also + // 2. The type of a field in the current declaring type, and + // 3. The field name starts with `val$`. + + int parametersEnd = parameters.Length; + if (parametersEnd == 0 || + !IsConstructor || + !DeclaringType.TryGetEnclosingMethodInfo (out var declaringClass, out var declaringMethodName, out var declaringMethodDescriptor) || + string.IsNullOrEmpty (declaringMethodDescriptor)) + return parametersEnd; + + var enclosingMethodTypes = ExtractTypesFromSignature (declaringMethodDescriptor).ToList (); + var closureFieldsTypes = DeclaringType.Fields + .Where (f => f.Name.StartsWith ("val$", StringComparison.Ordinal) && + f.AccessFlags.HasFlag (FieldAccessFlags.Final) && + f.AccessFlags.HasFlag (FieldAccessFlags.Synthetic)) + .Select (f => f.Descriptor) + .ToList (); + + for (int i = closureFieldsTypes.Count; i > 0; --i) { + if (!enclosingMethodTypes.Contains (closureFieldsTypes [i-1])) { + closureFieldsTypes.RemoveAt (i-1); + } + } + + for (int i = closureFieldsTypes.Count; i > 0; --i) { + if (parametersEnd == 0) + break; + if (parameters [parametersEnd-1].Type.BinaryName != closureFieldsTypes [i-1]) + break; + parametersEnd--; + } + return parametersEnd; + } + + void UpdateParametersFromSignature (ParameterInfo[] parameters) + { + var sig = GetSignature (); + if (sig == null) + return; + + int parametersCount = GetDeclaredParametersCount (parameters); + CheckDescriptorVariablesToSignatureParameters (parameters, parametersCount, sig); + int max = Math.Min (parametersCount, sig.Parameters.Count); + for (int i = 0; i < max; ++i) { + parameters [i].Type.TypeSignature = sig.Parameters [i]; + } + } + + void CheckDescriptorVariablesToLocalVariables (ParameterInfo[] parameters, int parametersCount, List names, int namesStart) + { + if (AccessFlags.HasFlag (MethodAccessFlags.Synthetic)) + return; + if ((names.Count - namesStart) == parametersCount) + return; + if (IsEnumCtor) + return; + + var paramsDesc = CreateParametersList (parameters, (v, i) => $"`{v.Type.BinaryName}` {v.Name}{(i >= parametersCount ? " /* abi; ignored */" : "")}"); + var localsDesc = CreateParametersList (names, (v, i) => $"`{v.Descriptor}` {v.Name}{(i < namesStart ? " /* abi; skipped */" : "")}"); + + Log.Debug ($"class-parse: method {DeclaringType.ThisClass.Name.Value}.{Name}.{Descriptor}: namesStart={namesStart}; " + + $"Local variables array has {names.Count - namesStart} entries {localsDesc}; " + + $"descriptor has {parametersCount} entries {paramsDesc}!"); + } + + static string CreateParametersList(IEnumerable values, Func createElement) + { + var description = new StringBuilder () + .Append ("("); + + int index = 0; + var first = true; + foreach (var v in values) { + if (!first) { + description.Append (", "); + } + first = false; + description.Append (createElement (v, index)); + index++; + } + description.Append (")"); + + return description.ToString (); + } + + void CheckLocalVariableTypeToDescriptorType (int index, ParameterInfo[] parameters, List names, int namesStart) + { + if (AccessFlags.HasFlag (MethodAccessFlags.Synthetic)) + return; + + var parameterType = parameters [index].Type.BinaryName; + var descriptorType = names [index + namesStart].Descriptor; + if (parameterType == descriptorType) + return; + + var paramsDesc = CreateParametersList (parameters, (v, i) => $"`{v.Type.BinaryName}` {v.Name}"); + var localsDesc = CreateParametersList (names, (v, i) => $"`{v.Descriptor}` {v.Name}{(i < namesStart ? " /* abi; skipped */" : "")}"); + + Log.Debug ($"class-parse: method {DeclaringType.ThisClass.Name.Value}.{Name}.{Descriptor}: " + + $"Local variables array {localsDesc} element {index+namesStart} with type `{descriptorType}` doesn't match expected descriptor list {paramsDesc} element {index} with type `{parameterType}`."); + } + + void CheckDescriptorVariablesToSignatureParameters (ParameterInfo[] parameters, int parametersCount, MethodTypeSignature sig) + { + if (IsEnumCtor) + return; + if (sig.Parameters.Count == parametersCount) + return; + + Log.Debug ($"class-parse: method {DeclaringType.ThisClass.Name.Value}.{Name}.{Descriptor}: " + + $"Signature ('{Attributes.Get()}') has {sig.Parameters.Count} entries; " + + $"Descriptor '{Descriptor}' has {parametersCount} entries!"); + } + public List GetThrows () { var throws = new List (); @@ -196,8 +353,7 @@ public List GetThrows () return throws; if (signature.Throws.Count > 0 && throws.Count != signature.Throws.Count) { - Log.Warning (1, "class-parse: warning: differing number of `throws` declarations on `{0}{1}`!", - Name, Descriptor); + Log.Warning (1, $"class-parse: warning: differing number of `throws` declarations on `{Name}{Descriptor}`!"); } int c = Math.Min (signature.Throws.Count, throws.Count); for (int i = 0; i < c; ++i) @@ -230,7 +386,8 @@ void UpdateParametersFromMethodParametersAttribute (ParameterInfo[] parameters) 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) { + int end = Math.Min (parameters.Length, pinfo.Count - startIndex); + for (int i = 0; i < end; ++i) { var p = pinfo [i + startIndex]; parameters [i].AccessFlags = p.AccessFlags; diff --git a/tests/Xamarin.Android.Tools.Bytecode-Tests/ClassFileFixture.cs b/tests/Xamarin.Android.Tools.Bytecode-Tests/ClassFileFixture.cs index 741f5de80..ba379c2b5 100644 --- a/tests/Xamarin.Android.Tools.Bytecode-Tests/ClassFileFixture.cs +++ b/tests/Xamarin.Android.Tools.Bytecode-Tests/ClassFileFixture.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; @@ -11,16 +12,69 @@ namespace Xamarin.Android.Tools.BytecodeTests { public class ClassFileFixture { + static void OnLog (TraceLevel level, int verbosity, string format, object[] args) + { + var message = string.Format (format, args); + + // Not sure how or why, but when we Assert.Fail() for Kotlin-related messages, instead of outright failing the test, we can get "bizarre" test failures. + // + // This is reasonable: + // 1-1) Failed : Xamarin.Android.Tools.BytecodeTests.JvmOverloadsConstructorTests.XmlDeclaration_WithJavaType_class + // TraceLevel=Verbose, Verbosity=0, Message=Kotlin: Hiding synthetic default constructor in class 'JvmOverloadsConstructor' with signature '(LJvmOverloadsConstructor;IILjava/lang/String;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V' + // at Xamarin.Android.Tools.BytecodeTests.ClassFileFixture.OnLog (System.Diagnostics.TraceLevel level, System.Int32 verbosity, System.String format, System.Object[] args) + // at Xamarin.Android.Tools.Bytecode.Log.Debug (System.String format, System.Object[] args) + // at Xamarin.Android.Tools.Bytecode.KotlinFixups.FixupJavaMethods (Xamarin.Android.Tools.Bytecode.Methods methods) + // at Xamarin.Android.Tools.Bytecode.KotlinFixups.Fixup (System.Collections.Generic.IList`1[T] classes) + // at Xamarin.Android.Tools.Bytecode.ClassPath.ToXElement () + // at Xamarin.Android.Tools.Bytecode.ClassPath.SaveXmlDescription (System.IO.TextWriter textWriter) + // at Xamarin.Android.Tools.BytecodeTests.ClassFileFixture.AssertXmlDeclaration (System.String classResource, System.String xmlResource, System.String documentationPath) + // at Xamarin.Android.Tools.BytecodeTests.JvmOverloadsConstructorTests.XmlDeclaration_WithJavaType_class () + // + // This is *confusing*; how is `//@synthetic` changing?! + // 1-2) Failed : Xamarin.Android.Tools.BytecodeTests.JvmOverloadsConstructorTests.XmlDeclaration_WithJavaType_class + // Expected string length 3509 but was 4633. Strings differ at index 2364. + // Expected: "...ge="false"\n synthetic="false"\n jni-signature..." + // But was: "...ge="false"\n synthetic="true"\n jni-signature=..." + // ---------------------------------------------^ + // at Xamarin.Android.Tools.BytecodeTests.ClassFileFixture.AssertXmlDeclaration (System.String classResource, System.String xmlResource, System.String documentationPath) + // at Xamarin.Android.Tools.BytecodeTests.JvmOverloadsConstructorTests.XmlDeclaration_WithJavaType_class () + // + // Ignore Kotlin-related messages for now + + if (message.IndexOf ("Kotlin", StringComparison.OrdinalIgnoreCase) >= 0) + return; + Assert.Fail ($"TraceLevel={level}, Verbosity={verbosity}, Message={message}"); + } + + [SetUp] + public void CreateLogger () + { + Log.OnLog = OnLog; + } + + [TearDown] + public void DestroyLogger () + { + Log.OnLog = null; + } + protected static ClassFile LoadClassFile (string resource) { using (var stream = GetResourceStream (resource)) { + if (stream == null) { + throw new InvalidOperationException ($"Could not find resource `{resource}`!"); + } return new ClassFile (stream); } } protected static string LoadString (string resource) { - using (var s = GetResourceStream (resource)) + var s = GetResourceStream (resource); + if (s == null) { + throw new InvalidOperationException ($"Could not find resource `{resource}`!"); + } + using (s) using (var r = new StreamReader (s)) return r.ReadToEnd (); } diff --git a/tests/Xamarin.Android.Tools.Bytecode-Tests/ConfiguredJdkInfo.cs b/tests/Xamarin.Android.Tools.Bytecode-Tests/ConfiguredJdkInfo.cs new file mode 100644 index 000000000..9126fbeaa --- /dev/null +++ b/tests/Xamarin.Android.Tools.Bytecode-Tests/ConfiguredJdkInfo.cs @@ -0,0 +1,54 @@ +using System; +using System.IO; +using System.Linq; +using System.Xml.Linq; + +using Xamarin.Android.Tools; + +namespace Xamarin.Android.Tools.BytecodeTests { + + static class ConfiguredJdkInfo { + + static JdkInfo info; + + static ConfiguredJdkInfo () + { + var jdkPath = ReadJavaSdkDirectoryFromJdkInfoProps (); + if (jdkPath == null) + return; + info = new JdkInfo (jdkPath); + } + + public static Version Version => info?.Version; + + static string ReadJavaSdkDirectoryFromJdkInfoProps () + { + var location = typeof (ConfiguredJdkInfo).Assembly.Location; + var binDir = Path.GetDirectoryName (Path.GetDirectoryName (location)); + var testDir = Path.GetFileName (Path.GetDirectoryName (location)); + if (!testDir.StartsWith ("Test", StringComparison.OrdinalIgnoreCase)) { + return null; + } + var buildName = testDir.Replace ("Test", "Build"); + if (buildName.IndexOf ("-", StringComparison.Ordinal) >= 0) { + buildName = buildName.Substring (0, buildName.IndexOf ('-')); + } + var jdkPropFile = Path.Combine (binDir, buildName, "JdkInfo.props"); + if (!File.Exists (jdkPropFile)) { + return null; + } + + var msbuild = XNamespace.Get ("http://schemas.microsoft.com/developer/msbuild/2003"); + + var jdkProps = XDocument.Load (jdkPropFile); + var jdkPath = jdkProps.Elements () + .Elements (msbuild + "PropertyGroup") + .Elements (msbuild + "JavaSdkDirectory") + .FirstOrDefault (); + if (jdkPath == null) { + return null; + } + return jdkPath.Value; + } + } +} diff --git a/tests/Xamarin.Android.Tools.Bytecode-Tests/ExpectedMethodDeclaration.cs b/tests/Xamarin.Android.Tools.Bytecode-Tests/ExpectedMethodDeclaration.cs index 9b2de077e..5d7527e29 100644 --- a/tests/Xamarin.Android.Tools.Bytecode-Tests/ExpectedMethodDeclaration.cs +++ b/tests/Xamarin.Android.Tools.Bytecode-Tests/ExpectedMethodDeclaration.cs @@ -43,7 +43,9 @@ public void Assert (MethodInfo method) } var parameters = method.GetParameters (); - NAssert.AreEqual (Parameters.Count, parameters.Length, $"Method {Name} Parameter Count"); + NAssert.AreEqual (Parameters.Count, parameters.Length, $"Method {Name} Parameter Count\n" + + $"Expected signature: {string.Join (", ", Parameters.Select (p => $"{p.Type.TypeSignature}: {p.Name}"))}\n" + + $" Actual signature: {string.Join (", ", parameters.Select (p => $"{p.Type.TypeSignature}: {p.Name}"))}"); for (int i = 0; i < Parameters.Count; ++i) { NAssert.AreEqual (Parameters [i].Name, parameters [i].Name, message); NAssert.AreEqual (i, parameters [i].Position, message); diff --git a/tests/Xamarin.Android.Tools.Bytecode-Tests/JavaType.1MyStringListTests.cs b/tests/Xamarin.Android.Tools.Bytecode-Tests/JavaType.1MyStringListTests.cs new file mode 100644 index 000000000..f9416499a --- /dev/null +++ b/tests/Xamarin.Android.Tools.Bytecode-Tests/JavaType.1MyStringListTests.cs @@ -0,0 +1,134 @@ +using System; + +using Xamarin.Android.Tools.Bytecode; + +using NUnit.Framework; + +namespace Xamarin.Android.Tools.BytecodeTests { + + [TestFixture] + public class JavaType_1MyStringListTests : ClassFileFixture { + + const string JavaType = "JavaType$1MyStringList"; + + [Test] + public void ClassFileDescription () + { + var jdk8 = ConfiguredJdkInfo.Version == null ? false : ConfiguredJdkInfo.Version < new Version (1, 9); + var vallist = jdk8 + ? new ParameterInfo ("val$value1", "Ljava/util/List;", "Ljava/util/List;") + : new ParameterInfo ("val$unboundedList", "Ljava/util/List;", "Ljava/util/List;"); + var valobj = jdk8 + ? new ParameterInfo ("val$unboundedList", "Ljava/lang/Object;", "Ljava/lang/Object;") + : new ParameterInfo ("val$value1", "Ljava/lang/Object;", "Ljava/lang/Object;"); + var c = LoadClassFile (JavaType + ".class"); + new ExpectedTypeDeclaration { + MajorVersion = 0x34, + MinorVersion = 0, + ConstantPoolCount = 73, + AccessFlags = ClassAccessFlags.Super, + FullName = "com/xamarin/JavaType$1MyStringList", + Superclass = new TypeInfo ("java/util/ArrayList", "Ljava/util/ArrayList;"), + InnerClasses = { + new ExpectedInnerClassInfo { + InnerClassName = "com/xamarin/JavaType$1MyStringList", + OuterClassName = null, + InnerName = "MyStringList", + AccessFlags = 0, + }, + }, + Fields = { + new ExpectedFieldDeclaration { + Name = "val$unboundedList", + Descriptor = "Ljava/util/List;", + AccessFlags = FieldAccessFlags.Final | FieldAccessFlags.Synthetic, + }, + new ExpectedFieldDeclaration { + Name = "val$value1", + Descriptor = "Ljava/lang/Object;", + AccessFlags = FieldAccessFlags.Final | FieldAccessFlags.Synthetic, + }, + }, + Methods = { + new ExpectedMethodDeclaration { + Name = "", + AccessFlags = MethodAccessFlags.Public, + ReturnDescriptor = "V", + Parameters = { + // "actual" parameters: + new ParameterInfo ("a", "Ljava/lang/String;", "Ljava/lang/String;"), + new ParameterInfo ("b", "I", "I"), + // declaring method parameters: + new ParameterInfo ("val$unboundedList", "Ljava/util/List;", "Ljava/util/List;"), + new ParameterInfo ("val$value1", "Ljava/lang/Object;", "Ljava/lang/Object;"), + }, + }, + new ExpectedMethodDeclaration { + Name = "", + AccessFlags = MethodAccessFlags.Public, + ReturnDescriptor = "V", + Parameters = { + // "actual" parameters: + new ParameterInfo ("value1", "Ljava/lang/Object;", "TT;"), + new ParameterInfo ("a", "Ljava/lang/String;", "Ljava/lang/String;"), + new ParameterInfo ("b", "I", "I"), + // declaring method parameters: + vallist, + valobj, + }, + }, + new ExpectedMethodDeclaration { + Name = "", + AccessFlags = MethodAccessFlags.Public, + ReturnDescriptor = "V", + Parameters = { + // "actual" parameters: + new ParameterInfo ("a", "Ljava/lang/String;", "Ljava/lang/String;"), + new ParameterInfo ("value2", "Ljava/lang/Number;", "TTExtendsNumber;"), + new ParameterInfo ("b", "I", "I"), + // declaring method parameters: + vallist, + valobj, + }, + }, + new ExpectedMethodDeclaration { + Name = "", + AccessFlags = MethodAccessFlags.Public, + ReturnDescriptor = "V", + Parameters = { + // "actual" parameters: + new ParameterInfo ("a", "Ljava/lang/String;", "Ljava/lang/String;"), + new ParameterInfo ("b", "I", "I"), + new ParameterInfo ("unboundedList", "Ljava/util/List;", "Ljava/util/List<*>;"), + // declaring method parameters: + vallist, + valobj, + }, + }, + new ExpectedMethodDeclaration { + Name = "get", + AccessFlags = MethodAccessFlags.Public, + ReturnDescriptor = "Ljava/lang/String;", + Parameters = { + new ParameterInfo ("index", "I", "I"), + }, + }, + new ExpectedMethodDeclaration { + Name = "get", + AccessFlags = MethodAccessFlags.Public | MethodAccessFlags.Bridge | MethodAccessFlags.Synthetic, + ReturnDescriptor = "Ljava/lang/Object;", + Parameters = { + new ParameterInfo ("index", "I", "I"), + }, + }, + } + }.Assert (c); + } + + [Test] + public void XmlDescription () + { + AssertXmlDeclaration (JavaType + ".class", JavaType + ".xml"); + } + } +} diff --git a/tests/Xamarin.Android.Tools.Bytecode-Tests/JavaTypeTests.cs b/tests/Xamarin.Android.Tools.Bytecode-Tests/JavaTypeTests.cs index e6a34a5e3..2c6c7a873 100644 --- a/tests/Xamarin.Android.Tools.Bytecode-Tests/JavaTypeTests.cs +++ b/tests/Xamarin.Android.Tools.Bytecode-Tests/JavaTypeTests.cs @@ -20,7 +20,7 @@ public void ClassFile_WithJavaType_class () new ExpectedTypeDeclaration { MajorVersion = 0x34, MinorVersion = 0, - ConstantPoolCount = 195, + ConstantPoolCount = 198, AccessFlags = ClassAccessFlags.Public | ClassAccessFlags.Super, FullName = "com/xamarin/JavaType", Superclass = new TypeInfo ("java/lang/Object", "Ljava/lang/Object;"), @@ -54,6 +54,12 @@ public void ClassFile_WithJavaType_class () InnerName = "PSC", AccessFlags = ClassAccessFlags.Public | ClassAccessFlags.Static | ClassAccessFlags.Abstract, }, + new ExpectedInnerClassInfo { + InnerClassName = "com/xamarin/JavaType$1MyStringList", + OuterClassName = null, + InnerName = "MyStringList", + AccessFlags = 0, + }, new ExpectedInnerClassInfo { InnerClassName = "com/xamarin/JavaType$1", OuterClassName = null, @@ -138,7 +144,7 @@ public void ClassFile_WithJavaType_class () Name = "STATIC_FINAL_STRING", Descriptor = "Ljava/lang/String;", AccessFlags = FieldAccessFlags.Public | FieldAccessFlags.Static | FieldAccessFlags.Final, - ConstantValue = "String(stringIndex=185 Utf8=\"Hello, \\\"embedded\0Nulls\" and \ud83d\udca9!\")", + ConstantValue = "String(stringIndex=188 Utf8=\"Hello, \\\"embedded\0Nulls\" and \ud83d\udca9!\")", }, new ExpectedFieldDeclaration { Name = "STATIC_FINAL_BOOL_FALSE", diff --git a/tests/Xamarin.Android.Tools.Bytecode-Tests/Resources/JavaType$1MyStringList.xml b/tests/Xamarin.Android.Tools.Bytecode-Tests/Resources/JavaType$1MyStringList.xml new file mode 100644 index 000000000..f366b2233 --- /dev/null +++ b/tests/Xamarin.Android.Tools.Bytecode-Tests/Resources/JavaType$1MyStringList.xml @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/Xamarin.Android.Tools.Bytecode-Tests/Xamarin.Android.Tools.Bytecode-Tests.csproj b/tests/Xamarin.Android.Tools.Bytecode-Tests/Xamarin.Android.Tools.Bytecode-Tests.csproj index 5db1b56cf..0601462d4 100644 --- a/tests/Xamarin.Android.Tools.Bytecode-Tests/Xamarin.Android.Tools.Bytecode-Tests.csproj +++ b/tests/Xamarin.Android.Tools.Bytecode-Tests/Xamarin.Android.Tools.Bytecode-Tests.csproj @@ -19,6 +19,7 @@ + @@ -31,6 +32,7 @@ + @@ -45,25 +47,6 @@ - - - - - - - - - - - - - - - - + diff --git a/tests/Xamarin.Android.Tools.Bytecode-Tests/Xamarin.Android.Tools.Bytecode-Tests.targets b/tests/Xamarin.Android.Tools.Bytecode-Tests/Xamarin.Android.Tools.Bytecode-Tests.targets new file mode 100644 index 000000000..f28e526f7 --- /dev/null +++ b/tests/Xamarin.Android.Tools.Bytecode-Tests/Xamarin.Android.Tools.Bytecode-Tests.targets @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/tests/Xamarin.Android.Tools.Bytecode-Tests/java/com/xamarin/JavaType.java b/tests/Xamarin.Android.Tools.Bytecode-Tests/java/com/xamarin/JavaType.java index d87cacb49..cb233ae38 100644 --- a/tests/Xamarin.Android.Tools.Bytecode-Tests/java/com/xamarin/JavaType.java +++ b/tests/Xamarin.Android.Tools.Bytecode-Tests/java/com/xamarin/JavaType.java @@ -1,6 +1,7 @@ package com.xamarin; import java.util.ArrayList; +import java.util.Collection; import java.util.List; /** @@ -199,6 +200,21 @@ void staticActionWithGenerics ( List extendsList, List superList) throws IllegalArgumentException, NumberFormatException, TThrowable { + + class MyStringList extends ArrayList { + public MyStringList(String a, int b) { + } + public MyStringList(T value1, String a, int b) { + } + public MyStringList(String a, TExtendsNumber value2, int b) { + } + public MyStringList(String a, int b, List unboundedList) { + } + public String get(int index) { + unboundedList.add (null); + return value1.toString(); + } + } } /** JNI sig: instanceActionWithGenerics.(Ljava/lang/Object;java/lang/Object;)V */ diff --git a/tools/class-parse/Program.cs b/tools/class-parse/Program.cs index d3994a1b2..741efdd45 100644 --- a/tools/class-parse/Program.cs +++ b/tools/class-parse/Program.cs @@ -106,7 +106,7 @@ static void DumpFileToXml (ClassPath jar, string file) return; } } - if (ClassPath.IsJarFile (file)) { + if (ClassPath.IsJmodFile (file) || ClassPath.IsJarFile (file)) { jar.Load (file); return; }