奇怪的“!*”在使用eclipse编译器编译时,在LocalVariableTypeTable中输入 [英] Strange "!*" entry in LocalVariableTypeTable when compiling with eclipse compiler

查看:785
本文介绍了奇怪的“!*”在使用eclipse编译器编译时,在LocalVariableTypeTable中输入的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们从Eclipse Mars.2 bundle中编译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 。目的); 
描述符:(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>

后果



上面仅描述和解释了现状,表明没有规范告诉编译器的行为方式不同从当前状态。显然,这不是一个完全可取的情况。


  1. 有人可能想联系Oracle,提到Java 8引入了一个没有涵盖的情况部分JVMS。一旦局部变量变成类型推断,这种情况可能会变得更加相关。

  2. 任何受到当前情况的负面影响的人都被邀请在 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 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 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.

  1. 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
  2. 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆