Commit 8ccb837
authored
[class-parse]
Context: #858
Context: https://stackoverflow.com/questions/44732915/why-did-java-9-introduce-the-jmod-file-format/64202720#64202720
Context: https://openjdk.java.net/projects/jigsaw/
Context: https://github.com/xamarin/monodroid/commit/c9e5cbd61fe80e183b44356149abe95578a13d06
JDK 9 replaced the "venerable" (and huge, ~63MB) `jre/lib/rt.jar`
with a set of `.jmod` files.
Thus, as of JDK 9, there is no `.jar` file to try to parse with
`class-parse`, only `.jmod` files!
A `.jmod` file, in turn, is still a ZIP container, much like `.jar`
files, but:
1. With a different internal directory structure, and
2. With a custom file header.
The result of (2) is that while `unzip -l` can show and extract the
contents of a `.jmod` file -- with a warning --
`System.IO.Compression.ZipArchive` cannot process the file:
% mono …/class-parse.exe $HOME/android-toolchain/jdk-11/jmods/java.base.jmod
class-parse: Unable to read file 'java.base.jmod': Number of entries expected in End Of Central Directory does not correspond to number of entries in Central Directory.
<api
api-source="class-parse" />
Update `Xamarin.Android.Tools.Bytecode.ClassPath` to support `.jmod`
files by using `PartialStream` (73096d9) to skip the first 4 bytes.
Once able to read a `.jmod` file, lots of debug messages appeared
while parsing `java.base.jmod`, a'la:
class-parse: method com/xamarin/JavaType$1MyStringList.<init>(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V:
Local variables array has 2 entries ('LocalVariableTableAttribute(
LocalVariableTableEntry(Name='this', Descriptor='Lcom/xamarin/JavaType$1MyStringList;', StartPC=0, Index=0),
LocalVariableTableEntry(Name='this$0', Descriptor='Lcom/xamarin/JavaType;', StartPC=0, Index=1),
LocalVariableTableEntry(Name='a', Descriptor='Ljava/lang/String;', StartPC=0, Index=2),
LocalVariableTableEntry(Name='b', Descriptor='I', StartPC=0, Index=3))'
); descriptor has 3 entries!
class-parse: method com/xamarin/JavaType$1MyStringList.<init>(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V:
Signature ('Signature((Ljava/lang/String;I)V)') has 2 entries; Descriptor '(Lcom/xamarin/JavaType;Ljava/lang/String;ILjava/lang/StringBuilder;)V' has 3 entries!
This was a variation on the "JDK 8?" block that previously didn't
have much detail, in part because it didn't have a repro. Now we
have a repro, based on [JDK code][0] which contains a class
declaration within a method declaration
// Java
public List<String> staticActionWithGenerics(…) {
class MyStringList extends ArrayList<String> {
public MyStringList(String a, int b) {
}
public String get(int index) {
return unboundedList.toString() + value1.toString();
}
}
}
The deal is that `staticActionWithGenerics()` contains a `MyStringList`
class, which in turn contains a constructor with two parameters.
*However*, as far as Java bytecode is concerned, the constructor
contains *3* local variables with StartPC==0, which is what we use to
infer parameter names.
Refactor, cleanup, and otherwise modify huge swaths of `Methods.cs`
to get to a "happy medium" of:
* No warnings from our unit tests, ensured by updating
`ClassFileFixture` to have a `[SetUp]` method which sets the
`Log.OnLog` field to a delegate which may call `Assert.Fail()`
when invoked. This asserts for all messages starting with
`class-parse: methods`, which are produced by `Methods.cs`.
* No warnings when processing `java.base.jmod`:
% mono bin/Debug/class-parse.exe $HOME/android-toolchain/jdk-11/jmods/java.base.jmod >/dev/null
# no error messages
* No warnings when processing Android API-31:
% mono bin/Debug/class-parse.exe $HOME/android-toolchain/sdk/platforms/android-31/android.jar >/dev/null
# no error messages
Additionally, improve `Log.cs` so that there are `M(string)`
overloads for the existing `M(string, params object[])` methods.
This is a "sanity-preserving" change, as "innocuous-looking" code
such as `Log.Debug("{foo}")` will throw `FormatException` when the
`(string, params object[])` overload is used.
Aside: closures are *weird* and finicky.
Consider the following Java code:
class ClosureDemo {
public void m(String a) {
class Example {
public Example(int x) {
System.out.println (a);
}
}
}
}
It looks like the JNI signature for the `Example` constructor might
be `(I)V`, but isn't. It is instead:
(LClosureDemo;ILjava/lang/String;)V
Breaking that down:
* `LClosureDemo;`: `Example` is an inner class, and thus has an
implicit reference to the containing type. OK, easy to forget.
* `I`: the `int x` parameter. Expected.
* `Ljava/lang/String`: the `String a` parameter from the enclosing
scope! This is the closure parameter.
This does make sense. The problem is that it's *selective*: only
variables used within `Example` become extra parameters.
If the `Example` constructor is updated to remove the
`System.out.println(a)` statement, then `a` is no longer used, and
is no longer present as a constructor parameter.
The only way I found to "reasonably" determine if a constructor
parameter was a closure parameter was by checking all fields in the
class with names starting with `val$`, and then comparing the types
of those fields to types within the enclosing method's descriptor.
I can't think of a way to avoid using `val$`. :-(
Another aside: closure parameter behavior *differs* between JDK 1.8
and JDK-11: there appears to be a JDK 1.8 `javac` bug in which it
assigns the *wrong* parameter names. Consider `MyStringList`:
The Java constructor declaration is:
public static <T, …>
void staticActionWithGenerics (
T value1, …
List<?> unboundedList, …)
{
class MyStringList extends ArrayList<String> {
public MyStringList(String a, int b) {
}
// …
}
}
The JNI signature for the `MyStringList` constructor is:
(Ljava/lang/String;ILjava/util/List;Ljava/lang/Object;)V
which is:
* `String`: parameter `a`
* `I`: parameter `b`
* `List`: closure parameter for `unboundedList`
* `Object`: closure parameter for `value1`.
If we build with JDK 1.8 `javac -parameters`, the `MethodParameters`
annotation states that the closure parameters are:
MyStringList(String a, int b, List val$value1, Object val$unboundedList);
which is *wrong*; `unboundedList` is the `List`, `value1` is `Object`!
This was fixed in JDK-11, with the `MethodParameters` annotations
specifying:
MyStringList(String a, int b, List val$unboundedList, Object val$value1);
This means that the unit tests need to take this into consideration.
Add a new `ConfiguredJdkInfo` class, which contains code similar to
`tests/TestJVM/TestJVM.cs`: it will read `bin/Build*/JdkInfo.props`
to find the path to the JDK found in `make prepare`, and then
determine the JDK version from that directory's `release` file.
[0]: https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/util/stream/WhileOps.java#L334.jmod file support (#891)1 parent 7c8d463 commit 8ccb837
File tree
15 files changed
+912
-106
lines changed- src/Xamarin.Android.Tools.Bytecode
- tests/Xamarin.Android.Tools.Bytecode-Tests
- Resources
- java/com/xamarin
- tools/class-parse
15 files changed
+912
-106
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
360 | 360 | | |
361 | 361 | | |
362 | 362 | | |
| 363 | + | |
| 364 | + | |
363 | 365 | | |
364 | 366 | | |
365 | 367 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
41 | 41 | | |
42 | 42 | | |
43 | 43 | | |
44 | | - | |
| 44 | + | |
45 | 45 | | |
46 | | - | |
47 | | - | |
48 | | - | |
49 | | - | |
50 | | - | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
51 | 58 | | |
| 59 | + | |
52 | 60 | | |
53 | 61 | | |
54 | 62 | | |
| |||
113 | 121 | | |
114 | 122 | | |
115 | 123 | | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
116 | 141 | | |
117 | 142 | | |
118 | 143 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
| 18 | + | |
| 19 | + | |
18 | 20 | | |
19 | 21 | | |
20 | 22 | | |
| |||
23 | 25 | | |
24 | 26 | | |
25 | 27 | | |
| 28 | + | |
| 29 | + | |
26 | 30 | | |
27 | 31 | | |
28 | 32 | | |
| |||
31 | 35 | | |
32 | 36 | | |
33 | 37 | | |
| 38 | + | |
| 39 | + | |
34 | 40 | | |
35 | 41 | | |
36 | 42 | | |
37 | 43 | | |
38 | 44 | | |
39 | 45 | | |
40 | 46 | | |
| 47 | + | |
| 48 | + | |
41 | 49 | | |
42 | 50 | | |
43 | 51 | | |
0 commit comments