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

时间:2016-05-17 06:09:02

标签: 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));
    }
}

编译命令如下:

$ 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   !*

我很惊讶地看到!*中的这个LocalVariableTypeTable条目。 JVM规范covers LocalVariableTypeTable属性并说:

  

该索引的constant_pool条目必须包含表示字段签名的CONSTANT_Utf8_info结构(§4.4.7),该字符签名对源程序中的局部变量类型进行编码({{3} })。

§4.7.9.1定义了字段签名的语法,如果我理解正确,则不包含与!*类似的任何内容。

还应该注意,javac编译器和旧的ECJ 3.10.x版本都不会生成此LocalVariableTypeTable条目。 !*是一些非标准的Eclipse扩展还是我在JVM规范中遗漏了什么?这是否意味着ECJ不符合JVM规范? !*实际上是什么意思,是否存在LocalVariableTypeTable属性中可能出现的其他类似字符串?

1 个答案:

答案 0 :(得分:6)

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

在内部,ecj使用两种CaptureBinding,一种用于实现,JLS 18.4调用“新鲜类型变量”,另一种用于实现captures a la JLS 5.1.10(使用相同的术语“自由类型变量“)。两者都使用!生成签名。仔细看看,在这个示例中,我们有一个“旧式”捕获:t类型为capture#1-of ?,捕获<T>中的Stream<T>

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

我无法让javac为lambda发出任何LocalVariableTypeTable,所以他们可能只是回避这个问题。

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

  

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

根据JLS,捕获是新的类型变量,因此LVTT条目很重要,JVMS中的一个遗漏是不指定此类型的格式。

后果

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

  1. 有人可能想联系Oracle,提到Java 8引入了JVMS部分未涵盖的情况。一旦局部变量也受到类型推断的影响,这种情况可能会变得更加相关
  2. 任何观察当前形势负面影响的人都会被邀请加入rfe 494198 (ecj),否则优先次序不高。
  3. <强>更新 同时有人报告了example,其中需要常规签名属性(不能被机会省略)来编码无法根据JVMS编码的类型。在那种情况下also javac creates unspecified byte code。根据{{​​3}} follow-up,我不认为这个讨论已经结束了(而且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)的未来版本已经no variable should ever have such a type。当javac和JLS相应修复时,我不能谈论时间表。