奇怪的“!*”在使用eclipse编译器编译时,在LocalVariableTypeTable中输入 [英] Strange "!*" entry in LocalVariableTypeTable when compiling with eclipse compiler
问题描述
import java.util.stream。* ;
public class Test {
String test(Stream<?> s){
return s.collect(Collector.of(() - >,(a ,t) - > {},(a1,a2)→a1));
}
}
编译命令如下:
$ java -jar org.eclipse.jdt.core_3.11.2.v20160128-0629.jar -8 -g Test.java
成功编译后,我们使用 javap -v -p Test.class
检查生成的类文件。最有趣的是为(a,t) - >生成的合成方法。 {}
lambda:
private static void lambda $ 1(java.lang.String,java.lang 。目的);
描述符:(Ljava / lang / String; Ljava / lang / Object;)V
标志:ACC_PRIVATE,ACC_STATIC,ACC_SYNTHETIC
代码:
stack = 0,locals = args_size = 2
0:return
LineNumberTable:
行5:0
LocalVariableTable:
开始长度插槽名称签名
0 1 0 a Ljava / lang /串;
0 1 1 t Ljava / lang / Object;
LocalVariableTypeTable:
开始长度插槽名称签名
0 1 1 t!*
$ b $在 LocalVariableTypeTable
中看到这个!*
条目,我感到非常惊讶。 JVM规范涵盖 LocalVariableTypeTable属性并说:
该索引的
constant_pool
条目必须包含一个CONSTANT_Utf8_info
结构(§4.4.7),表示对源程序中的局部变量的类型进行编码的字段签名(§4.7.9.1)。
§4.7.9.1定义了一个字段签名的语法,如果我理解正确,不覆盖任何类似于!还应该注意,javac编译器和旧的ECJ 3.10.x版本都不会生成这个
LocalVariableTypeTable
条目。一些非标准的Eclipse扩展,或者我在JVM规范中缺少某些东西,是!*
这是否意味着ECJ不符合JVM规范?什么!*
实际上意味着,还有其他类似的字符串可能出现在 LocalVariableTypeTable
属性?
ecj使用令牌!
来对通用签名中的捕获类型进行编码。因此,!*
表示捕获无界通配符。
在内部,ecj使用两种风格的 CaptureBinding
,一个实现,什么 JLS 18.4 调用新鲜类型变量,另一个实现捕获la JLS 5.1.10 (其使用相同的自由类型变量)。两者都使用!
生成签名。仔细看看,在这个例子中,我们有一个旧式捕获: t
有类型 capture#1-of?
,在 Stream< T>
中获取< T>
。
问题是: JVMS 4.7.9.1。似乎没有定义这种新鲜类型变量的编码(其他属性在源代码中没有对应关系,因此没有名称)。
我无法获取 javac
发出lambda的任何 LocalVariableTypeTable
,所以他们可能只是避免回答这个问题。
鉴于两个编译器都同意将 t
推断为捕获,为什么一个编译器生成一个LVTT,其他的没有? JVMS 4.7.14 有这个
这种差异对于类型使用类型变量或参数化类型的变量而言非常重要。
根据JLS,捕获是新鲜类型的变量,因此LVTT条目很重要,JVMS中没有指定此类型的格式是省略的。 / p>
后果
上面仅描述和解释了现状,表明没有规范告诉编译器的行为方式不同从当前状态。显然,这不是一个完全可取的情况。
- 有人可能想联系Oracle,提到Java 8引入了一个没有涵盖的情况部分JVMS。一旦局部变量变成类型推断,这种情况可能会变得更加相关。
- 任何受到当前情况的负面影响的人都被邀请在 rfe 494198(ecj),否则优先级较低。
更新:
同时有人报告了一个签名属性(不能机会地省略)的nofollow noreferrer示例对根据JVMS无法编码的类型进行编码。在这种情况下,也javac创建未指定的字节码。根据后续 no variable 应该有这样一种类型,但是我不认为这个讨论已经结束了,而且JLS还没有确定这个目标。
更新2:
从规范作者收到建议后,我会看到最终解决方案的三个部分:
(1)任何字节码属性中的每种类型签名必须符合JVMS 4.7.9.1中的语法。 ecj的!
和javac的<捕获的通配符>
是合法的。
$ b $ (2)编译器应该近似类型签名,其中不存在合法编码,例如,通过使用擦除而不是捕获。对于LVTT条目,这种近似值应该被认为是合法的。
(3)JLS必须确保仅使用JVMS编码的类型4.7.9.1
对于未来版本的ecj项目(1)和(2)已经解决。当javac和JLS将相应地被修改时,我不能说时间表。
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?
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.
这篇关于奇怪的“!*”在使用eclipse编译器编译时,在LocalVariableTypeTable中输入的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!