Java 奇怪*&引用;使用eclipse编译器编译时LocalVariableTypeTable中的项

Java 奇怪*&引用;使用eclipse编译器编译时LocalVariableTypeTable中的项,java,eclipse,lambda,bytecode,ecj,Java,Eclipse,Lambda,Bytecode,Ecj,让我们使用EclipseMars.2捆绑包中的ECJ编译器编译以下代码: import java.util.stream.*; public class Test { String test(Stream<?> s) { return s.collect(Collector.of(() -> "", (a, t) -> {}, (a1, a2) -> a1)); } } 看到这个,我非常惊讶*本地变量类型表中的条目。JVM规范Lo

让我们使用EclipseMars.2捆绑包中的ECJ编译器编译以下代码:

import java.util.stream.*;

public class Test {
    String test(Stream<?> s) {
        return s.collect(Collector.of(() -> "", (a, t) -> {}, (a1, a2) -> a1));
    }
}
看到这个
,我非常惊讶*本地变量类型表中的条目
。JVM规范LocalVariableTypeTable属性并说明:

该索引处的
constant\u pool
项必须包含一个
constant\u Utf8\u info
结构(),该结构表示对源程序中局部变量类型进行编码的字段签名()

定义字段签名的语法,如果我理解正确,它不包括任何类似于
的内容*


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

标记
由ecj用于在通用签名中对捕获类型进行编码。因此
*表示捕获无限通配符

在内部,ecj使用了两种风格的
CaptureBinding
,一种用于实现所谓的“新鲜类型变量”,另一种用于实现(使用与“自由类型变量”相同的行话)。两者都使用
生成签名。仔细看,在本例中,我们有一个“旧式”捕获:
t
具有类型
capture#1-of?
,在
流中捕获

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

我无法让
javac
为lambda发出任何
LocalVariableTypeTable
,因此他们可能只是避免回答这个问题

既然两个编译器都同意推断捕获的
t
,为什么一个编译器会生成LVTT,而另一个编译器不会?有这个吗

此差异仅对类型使用类型变量或参数化类型的变量显著

根据JLS,捕获是新类型变量,因此LVTT条目很重要,JVM中没有为这种类型指定格式是一个遗漏

后果 上面只描述和解释了现状,说明没有任何规范告诉编译器与当前状态不同的行为。显然,这不是一个完全可取的情况

  • 有人可能想联系Oracle,提到Java8引入了JVM部分未涉及的情况。一旦局部变量受到类型推断的影响,这种情况可能变得更加相关
  • 任何观察到当前局势的负面影响的人都被邀请插话,否则这一点就没有什么优先权
  • 更新: 同时,有人报告了一个错误,其中需要一个常规的签名属性(不能随意省略)来编码一个不能根据JVM编码的类型。那样的话。根据a,但我认为这场讨论还没有结束(无可否认,JLS尚未确保这一目标)

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

    (1) 任何字节码属性中的每个类型签名都必须遵守JVM 4.7.9.1中的语法。两个ecj都不是
    和javac的
    都是合法的

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

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


    对于ecj的未来版本,已修改了第(1)项和第(2)项。我不能谈论javac和JLS将被相应修复的时间表。

    它可能与那些bug和。这些“新类型变量”有助于验证
    Collector.of
    方法的泛型调用的正确性,但我看不出它们为什么会出现在局部变量表中。参数
    t
    不是泛型,它只是
    Object
    ,因为这是在该上下文中唯一有效的类型。值得注意的是,
    t
    是合成方法的一个参数,因此,如果
    t
    不是普通的
    对象
    ,ECJ必须提供一个描述通用参数的通用方法签名,但它没有。参见:“如果T是通配符参数化功能接口类型,且lambda表达式为隐式类型,则地面目标类型为T的非通配符参数化(§9.9)”,并且在§9.9末尾:“有时,可以从上下文(如lambda表达式的参数类型)中知道拟使用哪种函数类型(§15.27.3)。有时,需要选择一种;在这些情况下,使用边界。”一般来说,变量有一个真正的类型,而不是像
    CAP#1
    那样只存在于编译器中。因此JVM没有办法对这种非类型的东西进行编码也就不足为奇了。局部变量表除了调试之外没有其他用途,那么告诉调试器变量的类型有什么意义呢是
    CAP#1
    而不是
    还是仅仅是
    对象
    ?既然讨论形式规范真的超出了SO的范围,那么让我们关注一个实际问题来结束这个问题:既然
    LocalVariableTypeTable
    仅仅是为了调试目的而存在的:Eclipse会消除什么
      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   !*