Skip to content

Commit 77c9c5f

Browse files
authored
[class-parse] Import parameter names for unresolvable types (#921)
Fixes: #920 Context: 69e1b80 `java-source-utils.jar` isn't always able to fully resolve types. When it is unable to do so, it uses an "alternate encoding": > In some scenarios, types won't be resolvable. What should output be? > > We don't want to *require* that everything be resolvable -- it's painful, and > possibly impossible, e.g. w/ internal types -- so instead we should "demark" > the unresolvable types. > > `.params.txt` output will use `.*` as a type prefix, e.g. > > method(.*UnresolvableType foo, int bar); > > `docs.xml` will output `L.*UnresolvableType;`. The problem is that `class-parse --parameter-names=PATH` didn't check for this "unresolvable type" pattern, so if you had a method which contained them, e.g. the `target` parameter in: <interface jni-signature="Landroidx/core/view/NestedScrollingParent3;" name="NestedScrollingParent3"> <method jni-return="V" jni-signature="(L.*View;IIIII[I)V" name="onNestedScroll" return="void"> <parameter jni-type="L.*View;" name="target" type=".*View"/> <parameter jni-type="I" name="dxConsumed" type="int"/> <parameter jni-type="I" name="dyConsumed" type="int"/> <parameter jni-type="I" name="dxUnconsumed" type="int"/> <parameter jni-type="I" name="dyUnconsumed" type="int"/> <parameter jni-type="I" name="type" type="int"/> <parameter jni-type="[I" name="consumed" type="int[]"/> then `class-parse --parameter-names=params.xml lib.jar` wouldn't try to "loosely match" these parameter types. Consequently, parameter names were not imported. Update the `IJavaMethodParameterNameProvider.GetParameterNames()` method to take a new `JavaMethodParameterNameInfo` type, instead of five (5) parameters, as I wanted to increase the amount of data available to `GetParameterNames()`. Update `ApiXmlDocScraper.GetParameterNames()` twofold: 1. Use XLinq instead of computing an XPath expression, and 2. Loosely match parameter types when `//parameter/@type` starts with `.*`. This allows us to import parameter names for unresolvable types. "Force" the issue by adding `JavaInterfaceNoParameters.java` and a `ParameterFixup_JavaInterfaceNoParameters_JavaSourceUtils.xml`, generated via: java -jar bin/Debug/java-source-utils.jar \ tests/Xamarin.Android.Tools.Bytecode-Tests/java/com/xamarin/JavaInterfaceNoParameters.java \ > tests/Xamarin.Android.Tools.Bytecode-Tests/Resources/ParameterFixup_JavaInterfaceNoParameters_JavaSourceUtils.xml then altering the resulting XML so that instead of e.g. `Ljava/lang/Object;`, `L.*Object;` is used. This should permit appropriate parameter name overrides e.g. via: mono bin/Debug/class-parse.exe \ --parameter-names tests/Xamarin.Android.Tools.Bytecode-Tests/Resources/ParameterFixup_JavaInterfaceNoParameters_JavaSourceUtils.xml \ tests/Xamarin.Android.Tools.Bytecode-Tests/obj/Debug/classes/com/xamarin/JavaInterfaceNoParameters.class Testing found a deficiency in `java-source-utils`: it didn't properly represent "varargs" arrays. Update `tools/java-source-utils` so that params-arrays are properly represented.
1 parent a8b444d commit 77c9c5f

File tree

11 files changed

+260
-62
lines changed

11 files changed

+260
-62
lines changed

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

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -304,14 +304,23 @@ void FixupParametersFromDocs (XElement api, string path)
304304
if (!parameterElements.Select (x => x.Attribute ("name")?.Value).Any (p => p != null && IsGeneratedName (p)))
305305
continue;
306306

307-
var parameters = parameterElements
308-
.Select (p => p.Attribute ("type")?.Value!)
309-
.Where (p => p != null);
307+
var parameterTypes = parameterElements
308+
.Select (p => new JavaMethodParameterTypeInfo ((string) p.Attribute ("jni-type"), (string) p.Attribute ("type")))
309+
.ToArray ();
310310

311-
if (!parameters.Any ())
311+
if (!parameterTypes.Any ())
312312
continue;
313313

314-
var pnames = jdoc.GetParameterNames (currentpackage, className, currentMethod, parameters.ToArray (), isVarArgs: false);
314+
var nameInfo = new JavaMethodParameterNameInfo (
315+
currentpackage,
316+
className,
317+
currentMethod,
318+
(string) method.Attribute ("jni-signature"),
319+
parameterTypes,
320+
isVarArgs: false
321+
);
322+
323+
var pnames = jdoc.GetParameterNames (nameInfo);
315324
if (pnames == null || pnames.Length != parameterElements.Count)
316325
continue;
317326
for (int i = 0; i < parameterElements.Count; i++) {

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

Lines changed: 88 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -193,9 +193,14 @@ protected virtual string StripTagsFromParameters (string value)
193193
{
194194
return value;
195195
}
196-
197-
public virtual String[]? GetParameterNames (string package, string type, string method, string[] ptypes, bool isVarArgs)
196+
197+
public virtual string[]? GetParameterNames (JavaMethodParameterNameInfo info)
198198
{
199+
var package = info.PackageName;
200+
var type = info.TypeName;
201+
var method = info.MethodName;
202+
var ptypes = info.ParameterTypes.Select (p => p.JavaType).ToArray ();
203+
199204
string path = package.Replace ('.', '/') + '/' + type.Replace ('$', '.') + ".html";
200205
string file = Path.Combine (root, path);
201206
if (!File.Exists (file)) {
@@ -301,9 +306,39 @@ public static void LoadXml (String filename)
301306
}
302307
}
303308

309+
public readonly struct JavaMethodParameterTypeInfo {
310+
public string JniType {get;}
311+
public string JavaType {get;}
312+
313+
public JavaMethodParameterTypeInfo (string jniType, string javaType)
314+
{
315+
JniType = jniType;
316+
JavaType = javaType;
317+
}
318+
}
319+
320+
public readonly struct JavaMethodParameterNameInfo {
321+
public string PackageName {get;}
322+
public string TypeName {get;}
323+
public string MethodName {get;}
324+
public string? MethodSignature {get;}
325+
public bool IsVarArgs {get;}
326+
public JavaMethodParameterTypeInfo[] ParameterTypes {get;}
327+
328+
public JavaMethodParameterNameInfo (string packageName, string typeName, string methodName, string? methodSignature, JavaMethodParameterTypeInfo[] parameterTypes, bool isVarArgs)
329+
{
330+
PackageName = packageName;
331+
TypeName = typeName;
332+
MethodName = methodName;
333+
MethodSignature = methodSignature;
334+
ParameterTypes = parameterTypes;
335+
IsVarArgs = isVarArgs;
336+
}
337+
}
338+
304339
public interface IJavaMethodParameterNameProvider
305340
{
306-
String[]? GetParameterNames (string package, string type, string method, string[] ptypes, bool isVarArgs);
341+
string[]? GetParameterNames (JavaMethodParameterNameInfo info);
307342
}
308343

309344
public static class JavaMethodParameterNameProvider {
@@ -360,39 +395,59 @@ public ApiXmlDocScraper (string apiXmlFile)
360395

361396
XDocument xdoc;
362397

363-
public string[]? GetParameterNames (string package, string type, string method, string[] ptypes, bool isVarArgs)
398+
public string[]? GetParameterNames (JavaMethodParameterNameInfo info)
364399
{
365-
var methodOrCtor = method == "constructor" ?
366-
"constructor[" : $"method[@name='{method}'";
367-
368-
var pcount = ptypes.Length;
369-
370-
var xpath = new StringBuilder ();
371-
372-
xpath.Append ($"/api/package[@name='{package}']/*[self::class or self::interface]/");
373-
374-
if (method == "constructor")
375-
xpath.Append ("constructor[");
376-
else
377-
xpath.Append ($"method[@name='{method}'");
378-
379-
xpath.Append ($" and count(parameter)={pcount}");
380-
381-
if (pcount > 0) {
382-
xpath.Append (" and ");
383-
xpath.Append (string.Join (" and ", ptypes.Select ((pt, pindex) => $"parameter[{pindex + 1}][@type='{pt}']")));
400+
var xtype = xdoc
401+
.Elements ("api")
402+
.Elements ("package")
403+
.Where (p => ((string) p.Attribute ("name")) == info.PackageName)
404+
.Elements ()
405+
.Where (t => ((string) t.Attribute ("name")) == info.TypeName)
406+
.FirstOrDefault ();
407+
if (xtype == null) {
408+
return null;
384409
}
385410

386-
xpath.Append ("]");
387-
388-
var methodElem = xdoc.XPathSelectElement (xpath.ToString ());
389-
390-
if (methodElem != null)
391-
return methodElem.Elements ("parameter")
392-
.Select (pe => pe.Attribute ("name")?.Value ?? "")
393-
.ToArray ();
394-
395-
return null;
411+
var members = info.MethodName == "constructor"
412+
? xtype.Elements ("constructor")
413+
: xtype.Elements ("method").Where (m => ((string) m.Attribute ("name")) == info.MethodName);
414+
var pcount = info.ParameterTypes.Length;
415+
members = members
416+
.Where (m => m.Elements ("parameter").Count () == pcount);
417+
418+
XElement? member =
419+
members.FirstOrDefault (m => info.MethodSignature == (string) m.Attribute ("jni-signature"));
420+
if (member == null) {
421+
foreach (var m in members) {
422+
var found = true;
423+
int i = 0;
424+
foreach (var p in m.Elements ("parameter")) {
425+
if (!ParameterTypesMatch (p, info.ParameterTypes [i++].JavaType)) {
426+
found = false;
427+
break;
428+
}
429+
}
430+
if (found) {
431+
member = m;
432+
break;
433+
}
434+
}
435+
}
436+
if (member == null)
437+
return null;
438+
return member.Elements ("parameter")
439+
.Select (p => (string) p.Attribute ("name"))
440+
.ToArray ();
441+
442+
bool ParameterTypesMatch (XElement parameter, string ptype)
443+
{
444+
var jtype = (string) parameter.Attribute ("type");
445+
if (!jtype.StartsWith (".*", StringComparison.Ordinal)) {
446+
return jtype == ptype;
447+
}
448+
jtype = "." + jtype.Substring (".*".Length);
449+
return ptype.EndsWith (jtype, StringComparison.Ordinal);
450+
}
396451
}
397452
}
398453
}

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,13 @@ List<Package> LoadParameterFixupDescription (string path)
114114
return fixup;
115115
}
116116

117-
public string[]? GetParameterNames (string package, string type, string method, string[] ptypes, bool isVarArgs)
117+
public string[]? GetParameterNames (JavaMethodParameterNameInfo info)
118118
{
119+
var package = info.PackageName;
120+
var type = info.TypeName;
121+
var method = info.MethodName;
122+
var ptypes = info.ParameterTypes.Select (p => p.JavaType).ToArray ();
123+
119124
var methods = this.packages
120125
.Where(p => p.Name == package && p.Types != null)
121126
.SelectMany(p => p.Types!)

tests/Xamarin.Android.Tools.Bytecode-Tests/ParameterFixupTests.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,21 @@ public void XmlDeclaration_FixedUpFromApiXmlDocumentation ()
4545
}
4646
}
4747

48+
[Test]
49+
public void XmlDeclaration_FixedUpFromUnresolvedApiXmlDocumentation ()
50+
{
51+
string docsPath = null;
52+
53+
try {
54+
docsPath = LoadToTempFile ("ParameterFixup_JavaInterfaceNoParameters_JavaSourceUtils.xml");
55+
56+
AssertXmlDeclaration ("JavaInterfaceNoParameters.class", "ParameterFixup_JavaInterfaceNoParameters.xml", docsPath);
57+
} finally {
58+
if (File.Exists (docsPath))
59+
File.Delete (docsPath);
60+
}
61+
}
62+
4863
[Test]
4964
public void XmlDeclaration_DoesNotThrowAnExceptionIfDocumentationNotFound ()
5065
{
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<api
2+
api-source="class-parse">
3+
<package
4+
name="com.xamarin"
5+
jni-name="com/xamarin">
6+
<interface
7+
abstract="true"
8+
deprecated="not deprecated"
9+
final="false"
10+
name="JavaInterfaceNoParameters"
11+
jni-signature="Lcom/xamarin/JavaInterfaceNoParameters;"
12+
source-file-name="JavaInterfaceNoParameters.java"
13+
static="false"
14+
visibility="public">
15+
<method
16+
abstract="true"
17+
deprecated="not deprecated"
18+
final="false"
19+
name="asList"
20+
native="false"
21+
return="java.util.List&lt;T&gt;"
22+
jni-return="Ljava/util/List&lt;TT;&gt;;"
23+
static="false"
24+
synchronized="false"
25+
visibility="public"
26+
bridge="false"
27+
synthetic="false"
28+
jni-signature="([Ljava/lang/Object;)Ljava/util/List;">
29+
<typeParameters>
30+
<typeParameter
31+
name="T"
32+
jni-classBound="Ljava/lang/Object;"
33+
classBound="java.lang.Object"
34+
interfaceBounds=""
35+
jni-interfaceBounds="" />
36+
</typeParameters>
37+
<parameter
38+
name="a"
39+
type="T..."
40+
jni-type="[TT;" />
41+
</method>
42+
<method
43+
abstract="true"
44+
deprecated="not deprecated"
45+
final="false"
46+
name="binarySearch"
47+
native="false"
48+
return="int"
49+
jni-return="I"
50+
static="false"
51+
synchronized="false"
52+
visibility="public"
53+
bridge="false"
54+
synthetic="false"
55+
jni-signature="([Ljava/lang/Object;IILjava/lang/Object;)I">
56+
<parameter
57+
name="a"
58+
type="java.lang.Object[]"
59+
jni-type="[Ljava/lang/Object;" />
60+
<parameter
61+
name="fromIndex"
62+
type="int"
63+
jni-type="I" />
64+
<parameter
65+
name="toIndex"
66+
type="int"
67+
jni-type="I" />
68+
<parameter
69+
name="key"
70+
type="java.lang.Object"
71+
jni-type="Ljava/lang/Object;" />
72+
</method>
73+
</interface>
74+
</package>
75+
</api>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2+
<api api-source="java-source-utils">
3+
<package jni-name="com/xamarin" name="com.xamarin">
4+
<interface jni-signature="Lcom/xamarin/JavaInterfaceNoParameters;" name="JavaInterfaceNoParameters">
5+
<method jni-return="L.*List;" jni-signature="([L.*Object;)L.*List;" name="asList" return=".*List&lt;T&gt;">
6+
<parameter jni-type="[L.*Object;" name="a" type="T..."/>
7+
<javadoc>
8+
<![CDATA[JNI sig: (Lcom/xamarin/JavaTypeNoParameters;)V]]>
9+
</javadoc>
10+
</method>
11+
<method jni-return="I" jni-signature="([L.*Object;IIL.*Object;)I" name="binarySearch" return="int">
12+
<parameter jni-type="[L.*Object;" name="a" type=".*Object[]"/>
13+
<parameter jni-type="I" name="fromIndex" type="int"/>
14+
<parameter jni-type="I" name="toIndex" type="int"/>
15+
<parameter jni-type="L.*Object;" name="key" type=".*Object"/>
16+
</method>
17+
</interface>
18+
</package>
19+
</api>

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
<EmbeddedResource Include="$(IntermediateOutputPath)classes\com\xamarin\IParameterInterface.class" />
3333
<EmbeddedResource Include="$(IntermediateOutputPath)classes\com\xamarin\JavaAnnotation.class" />
3434
<EmbeddedResource Include="$(IntermediateOutputPath)classes\com\xamarin\JavaEnum.class" />
35+
<EmbeddedResource Include="$(IntermediateOutputPath)classes\com\xamarin\JavaInterfaceNoParameters.class" />
3536
<EmbeddedResource Include="$(IntermediateOutputPath)classes\com\xamarin\JavaType%241.class" />
3637
<EmbeddedResource Include="$(IntermediateOutputPath)classes\com\xamarin\JavaType%241MyStringList.class" />
3738
<EmbeddedResource Include="$(IntermediateOutputPath)classes\com\xamarin\JavaType%24ASC.class" />
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.xamarin;
2+
3+
public interface JavaInterfaceNoParameters {
4+
/**
5+
* JNI sig: ([Ljava/lang/Object;)Ljava/util/List;
6+
*/
7+
<T> java.util.List<T> asList(T... a);
8+
9+
/**
10+
* JNI sig: ([Ljava/lang/Object;IILjava/lang/Object;)I
11+
*
12+
* @param a [Ljava/lang/Object;
13+
* @param fromIndex int
14+
* @param toIndex int
15+
* @param key Ljava/lang/Object
16+
* @return int
17+
*/
18+
int binarySearch(Object[] a, int fromIndex, int toIndex, Object key);
19+
}

0 commit comments

Comments
 (0)