Tagir Valeev Tagir Valeev - 6 months ago 9
Java Question

Strange "!*" entry in LocalVariableTypeTable when compiling with eclipse compiler

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 a
CONSTANT_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?

Answer

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.

  1. 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
  2. Anybody observing negative impact of the current situation is invited to chime in in rfe 494198 (ecj), which otherwise has low priority.