使用eclipse编译器编译时,LocalVariableTypeTable中的奇怪"!*"条目

Tag*_*eev 13 java eclipse lambda bytecode ecj

让我们用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));
    }
}
Run Code Online (Sandbox Code Playgroud)

编译命令如下:

$ 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.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   !*
Run Code Online (Sandbox Code Playgroud)

我很惊讶地看到这个!*条目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属性中?

Ste*_*ann 6

!ecj使用令牌来编码通用签名中的捕获类型.因此!*表示捕获无界通配符.

在内部,ecj使用两种方式CaptureBinding,一种用于实现,JLS 18.4称为"新类型变量",另一种用于实现捕获la JLS 5.1.10(使用相同的"自由类型变量").两者都使用产生签名!.仔细看看,在这个例子中,我们有一个"旧式"捕获:t有类型capture#1-of ?,捕获<T>in Stream<T>.

问题是:JVMS 4.7.9.1.似乎没有为这样的新类型变量定义编码(其他属性在源代码中没有对应关系,因此没有名称).

我无法为lambda javac发射任何东西LocalVariableTypeTable,所以他们可能只是避免回答这个问题.

鉴于两个编译器都同意推断t捕获,为什么一个编译器生成LVTT,而另一个编译器没有?JVMS 4.7.14有这个

这种差异仅对类型使用类型变量或参数化类型的变量有意义.

根据JLS,捕获是新的类型变量,因此LVTT条目是重要的,并且在JVMS中省略了不指定此类型的格式.

后果

以上仅描述和解释了现状,证明没有规范告诉编译器的行为与当前状态不同.显然,这不是一个完全理想的情况.

  1. 有人可能想联系Oracle,提到Java 8引入了JVMS部分未涵盖的情况.一旦局部变量受到类型推断的影响,这种情况可能变得更加相关
  2. 任何观察当前形势的负面影响的人都被邀请参加rfe 494198(ecj),否则该优先级不高.

更新: 同时有人报告了一个例子,其中需要一个常规的Signature属性(不能被机会省略)来编码一个不能根据JVMS编码的类型.在这种情况下,javac也会创建未指定的字节代码.根据后续行动, 任何变量都不应该有这样的类型,但我不认为这个讨论已经结束了(并且不可否认JLS还没有确保这个目标).

更新2: 在收到规范作者的建议后,我看到最终解决方案的三个部分:

(1)任何字节码属性中的每个类型签名都必须遵守JVMS 4.7.9.1中的语法.ecj !和javac 都不<captured wildcard>合法.

(2)编译器应该在没有合法编码的情况下近似类型签名,例如,使用擦除而不是捕获.对于LVTT条目,这种近似应被视为合法.

(3)JLS 必须确保只有使用JVMS 4.7.9.1可编码的类型出现在必须生成Signature属性的位置.

对于ecj项目(1)和(2)的未来版本已经解决.当javac和JLS相应修复时,我不能谈论时间表.

  • 见[JLS§15.27.3](https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.27.3):"*如果T是通配符 - 参数化的函数接口类型和lambda表达式是隐式类型的,然后地面目标类型是T*的非通配符参数化(第9.9节),并在§9.9的末尾:"*有时,可以从上下文,例如lambda表达式的参数类型,是指函数类型(第15.27.3节).其他时候,有必要选一个; 在这种情况下,使用界限.*" (2认同)
  • @ user882813感谢额外的测试用例.事实证明,LVTT条目只是"优化"出来,因为局部变量未被使用.如果使用该变量,则会正确生成LVTT条目.您可以通过https://bugs.eclipse.org/494225关注此问题的进展 (2认同)