使用 Eclipse 编译器编译时 LocalVariableTypeTable 中出现奇怪的“!*”条目
Strange "!*" entry in LocalVariableTypeTable when compiling with Eclipse compiler
让我们使用 Eclipse Mars.2 包中的 ECJ 编译器编译以下代码:
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
检查生成的class文件。最有趣的是为 (a, t) -> {}
lambda 生成的合成方法:
private static void lambda(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 !*
我很惊讶在 LocalVariableTypeTable
中看到这个 !*
条目。 JVM 规范 covers LocalVariableTypeTable 属性并表示:
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 定义了字段签名的语法,如果我理解正确的话,它不包含类似于 !*
.
的任何内容
还应注意,javac 编译器和较旧的 ECJ 3.10.x 版本都不会生成此 LocalVariableTypeTable
条目。 !*
是一些非标准的 Eclipse 扩展还是我在 JVM 规范中遗漏了什么?这是否意味着 ECJ 不符合 JVM 规范? !*
的实际含义是什么,是否还有其他类似的字符串可能出现在 LocalVariableTypeTable
属性中?
ecj 使用令牌 !
对通用签名中的捕获类型进行编码。因此 !*
表示捕获无限通配符。
在内部,ecj 使用两种风格的 CaptureBinding
,一种用于实现 JLS 18.4 calls "fresh type variables", the other to implement captures a la JLS 5.1.10(使用与 "free type variables" 相同的行话)。两者都使用 !
生成签名。仔细看看,在这个例子中我们有一个 "old-style" 捕获:t
有类型 capture#1-of ?
,捕获 Stream<T>
.
中的 <T>
问题是:JVMS 4.7.9.1. 似乎没有为此类新类型变量定义编码(这些变量在其他属性中在源代码中没有对应关系,因此没有名称)。
我无法让 javac
为 lambda 发出任何 LocalVariableTypeTable
,所以他们可能只是避免回答这个问题。
鉴于两个编译器都同意将 t
推断为捕获,为什么一个编译器生成 LVTT,而另一个编译器不生成? JVMS 4.7.14 有这个
This difference is only significant for variables whose type uses a type variable or parameterized type.
根据 JLS,捕获的是新的类型变量,因此 LVTT 条目很重要,JVMS 中没有为该类型指定格式是一个遗漏。
后果
以上仅描述和解释了现状,表明没有规范告诉编译器的行为与当前状态不同。显然,这不是一个完全理想的情况。
- 可能有人想联系Oracle,提到Java8引入了JVMS部分未涵盖的情况。一旦局部变量也受到类型推断的影响,这种情况可能会变得更加相关
- 任何观察到当前情况负面影响的人都被邀请加入 rfe 494198 (ecj),否则优先级较低。
更新:
与此同时,有人报告了一个 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,但我认为这个讨论还没有结束(诚然,JLS 还没有确保这个目标)。
更新二:
在收到规范作者的建议后,我看到了最终解决方案的三个部分:
(1) 任何字节码属性中的每个类型签名 必须 遵守 JVMS 4.7.9.1 中的语法。 ecj的!
和javac的<captured wildcard>
都不合法。
(2) 编译器 应该 在不存在合法编码的情况下近似类型签名,例如,通过使用擦除而不是捕获。对于 LVTT 条目,这种近似应该被认为是合法的。
(3) JLS 必须 确保只有可使用 JVMS 4.7.9.1 编码的类型出现在必须生成签名属性的位置。
未来版本的 ecj 项目 (1) 和 (2) 已经 resolved。我不能谈论 javac 和 JLS 何时会相应修复的时间表。
让我们使用 Eclipse Mars.2 包中的 ECJ 编译器编译以下代码:
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
检查生成的class文件。最有趣的是为 (a, t) -> {}
lambda 生成的合成方法:
private static void lambda(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 !*
我很惊讶在 LocalVariableTypeTable
中看到这个 !*
条目。 JVM 规范 covers LocalVariableTypeTable 属性并表示:
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 定义了字段签名的语法,如果我理解正确的话,它不包含类似于 !*
.
还应注意,javac 编译器和较旧的 ECJ 3.10.x 版本都不会生成此 LocalVariableTypeTable
条目。 !*
是一些非标准的 Eclipse 扩展还是我在 JVM 规范中遗漏了什么?这是否意味着 ECJ 不符合 JVM 规范? !*
的实际含义是什么,是否还有其他类似的字符串可能出现在 LocalVariableTypeTable
属性中?
ecj 使用令牌 !
对通用签名中的捕获类型进行编码。因此 !*
表示捕获无限通配符。
在内部,ecj 使用两种风格的 CaptureBinding
,一种用于实现 JLS 18.4 calls "fresh type variables", the other to implement captures a la JLS 5.1.10(使用与 "free type variables" 相同的行话)。两者都使用 !
生成签名。仔细看看,在这个例子中我们有一个 "old-style" 捕获:t
有类型 capture#1-of ?
,捕获 Stream<T>
.
<T>
问题是:JVMS 4.7.9.1. 似乎没有为此类新类型变量定义编码(这些变量在其他属性中在源代码中没有对应关系,因此没有名称)。
我无法让 javac
为 lambda 发出任何 LocalVariableTypeTable
,所以他们可能只是避免回答这个问题。
鉴于两个编译器都同意将 t
推断为捕获,为什么一个编译器生成 LVTT,而另一个编译器不生成? JVMS 4.7.14 有这个
This difference is only significant for variables whose type uses a type variable or parameterized type.
根据 JLS,捕获的是新的类型变量,因此 LVTT 条目很重要,JVMS 中没有为该类型指定格式是一个遗漏。
后果
以上仅描述和解释了现状,表明没有规范告诉编译器的行为与当前状态不同。显然,这不是一个完全理想的情况。
- 可能有人想联系Oracle,提到Java8引入了JVMS部分未涵盖的情况。一旦局部变量也受到类型推断的影响,这种情况可能会变得更加相关
- 任何观察到当前情况负面影响的人都被邀请加入 rfe 494198 (ecj),否则优先级较低。
更新: 与此同时,有人报告了一个 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,但我认为这个讨论还没有结束(诚然,JLS 还没有确保这个目标)。
更新二: 在收到规范作者的建议后,我看到了最终解决方案的三个部分:
(1) 任何字节码属性中的每个类型签名 必须 遵守 JVMS 4.7.9.1 中的语法。 ecj的!
和javac的<captured wildcard>
都不合法。
(2) 编译器 应该 在不存在合法编码的情况下近似类型签名,例如,通过使用擦除而不是捕获。对于 LVTT 条目,这种近似应该被认为是合法的。
(3) JLS 必须 确保只有可使用 JVMS 4.7.9.1 编码的类型出现在必须生成签名属性的位置。
未来版本的 ecj 项目 (1) 和 (2) 已经 resolved。我不能谈论 javac 和 JLS 何时会相应修复的时间表。