问题
Let's compile the following code with ECJ compiler from Eclipse Mars.2 bundle:
import java.util.stream.*;
public class Test {
String test(Stream<?> s) {
return s.collect(Collector.of(() -> "", (a, t) -> {}, (a1, a2) -> a1));
}
}
The compilation command is the following:
$ java -jar org.eclipse.jdt.core_3.11.2.v20160128-0629.jar -8 -g Test.java
After the successful compilation let's check the resulting class file with javap -v -p Test.class
. The most interesting is the synthetic method generated for the (a, t) -> {}
lambda:
private static void lambda$1(java.lang.String, java.lang.Object);
descriptor: (Ljava/lang/String;Ljava/lang/Object;)V
flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
Code:
stack=0, locals=2, args_size=2
0: return
LineNumberTable:
line 5: 0
LocalVariableTable:
Start Length Slot Name Signature
0 1 0 a Ljava/lang/String;
0 1 1 t Ljava/lang/Object;
LocalVariableTypeTable:
Start Length Slot Name Signature
0 1 1 t !*
I was quite surprised to see this !*
entry in LocalVariableTypeTable
. JVM specification covers LocalVariableTypeTable attribute and says:
The
constant_pool
entry at that index must contain aCONSTANT_Utf8_info
structure (§4.4.7) representing a field signature which encodes the type of a local variable in the source program (§4.7.9.1).
§4.7.9.1 defines a grammar for field signatures which, if I understand correctly, does not cover anything similar to !*
.
It should also be noted that neither javac compiler, nor older ECJ 3.10.x versions generate this LocalVariableTypeTable
entry. Is !*
some non-standard Eclipse extension or I'm missing something in JVM spec? Does this mean that ECJ does not conform to JVM spec? What !*
actually mean and are there any other similar strings which could appear in LocalVariableTypeTable
attribute?
回答1:
The token !
is used by ecj to encode a capture type in generic signatures. Hence !*
signifies a capture of an unbounded wildcard.
Internally, ecj uses two flavours of CaptureBinding
, one to implement, what JLS 18.4 calls "fresh type variables", the other to implement captures a la JLS 5.1.10 (which uses the same lingo of "free type variables"). Both produce a signature using !
. At a closer look, in this example we have an "old-style" capture: t
has type capture#1-of ?
, capturing the <T>
in Stream<T>
.
The problem is: JVMS 4.7.9.1. doesn't seem to define an encoding for such fresh type variables (which among other properties have no correspondence in source code and hence no name).
I couldn't get javac
to emit any LocalVariableTypeTable
for the lambda, so they might simply avoid answering this question.
Given that both compilers agree on inferring t
to a capture, why does one compiler generate a LVTT, where the other does not? JVMS 4.7.14 has this
This difference is only significant for variables whose type uses a type variable or parameterized type.
According to JLS, captures are fresh type variables, so an LVTT entry is significant, and it is an omission in JVMS not to specify a format for this type.
Consequences
The above only describes and explains the status quo, demonstrating that no specification tells a compiler to behave differently from current status. Obviously, this is not an entirely desirable situation.
- Someone may want to contact Oracle, mentioning that Java 8 introduces a situation that is not covered by parts of the JVMS. This situation may become even more relevant once also local variables become subject to type inference
- Anybody observing negative impact of the current situation is invited to chime in in rfe 494198 (ecj), which otherwise has low priority.
Update: Meanwhile someone has reported an example where a regular Signature attribute (which cannot be opportunistically omitted) is required to encode a type which cannot be encoded according to JVMS. In that case also javac creates unspecified byte code. According to a follow-up no variable should ever have such a type, but I don't think that this discussion is over, yet (and admittedly JLS doesn't yet ensure this goal).
Update 2: After receiving advice from a spec author I see three parts to the ultimate solution:
(1) Every type signature in any bytecode attribute must adhere to the grammar in JVMS 4.7.9.1. Neither ecj's !
nor javac's <captured wildcard>
is legal.
(2) Compilers should approximate type signatures where no legal encoding exists, e.g., by using the erasure instead of a capture. For an LVTT entry, such approximation should be considered as legitimate.
(3) JLS must ensure that only types encodable using JVMS 4.7.9.1 appear in positions where generating a Signature attribute is mandatory.
For future versions of ecj items (1) and (2) have been resolved. I cannot speak about schedules when javac and JLS will be fixed accordingly.
来源:https://stackoverflow.com/questions/37268391/strange-entry-in-localvariabletypetable-when-compiling-with-eclipse-compile