来自Groovy CompileStatic的Odd Bytecodes

时间:2014-11-30 03:08:58

标签: java groovy bytecode

当我查看使用@ groovy.transform.CompileStatic编译的Groovy脚本的字节码时,我看到了一些奇怪的结果

这是复制问题的最简单的类:

@groovy.transform.CompileStatic
class ScriptTestClass{
   void test_method(String x,String y,String z){
         x = "foo";
   }
}

当编译为字节码时,我得到这个字节码(javap -c -v ScriptTestClass.class的结果,仅针对主题方法编辑):

javap -c -v ScriptTestClass.class的结果(虽然只是主题方法):

  public void test_method(java.lang.String, java.lang.String, java.lang.String);
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=5, args_size=4
         0: ldc           #32                 // String foo
         2: astore        4
         4: aload         4
         6: astore_1      
         7: aload         4
         9: pop           
        10: return        
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0      10     0  this   LScriptTestClass;
               0      10     1     x   Ljava/lang/String;
               0      10     2     y   Ljava/lang/String;
               0      10     3     z   Ljava/lang/String;
      LineNumberTable:
        line 4: 0

显然, ASTORE / ALOAD 4 在这里不合适。事实上,它们看起来无关紧要。如果删除了字节码,则字节码是正确的。正确的字节码(我在Java类中编写相同的代码时得到的是:

  public void test_method(java.lang.String, java.lang.String, java.lang.String);
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=4, args_size=4
         0: ldc           #7                  // String foo
         2: astore_1      
         3: return        
      LineNumberTable:
        line 26: 0
        line 27: 3
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0       4     0  this   Ltesting/ScriptTestClass;
               0       4     1     x   Ljava/lang/String;
               0       4     2     y   Ljava/lang/String;
               0       4     3     z   Ljava/lang/String;

为什么@CompileStatic产生访问局部变量槽4的字节码?

我正在使用d Groovy版本2.3.7和Java 1.7

如果它是相关的,我用来从Groovy源获取字节的代码如下。

此代码将脚本解析为类并获取字节:

protected ClassNode loadGroovyTestClassAsBytecode(String classSource) throws Exception{
    ClassNode classNode = new ClassNode();
    String scriptName = "ScriptTestClass.groovy";      
    Class groovyClass = groovyClassLoader.parseClass(classSource,scriptName);        
    String className = groovyClass.getName() + ".class";
    byte[] classBytes = groovyClassLoader.getClassBytes(className);

}

' groovyClassLoader'上面是以下类加载器的实例,它允许在加载后获取字节:

public class CachingGroovyClassLoader extends GroovyClassLoader {

    private Map<String, byte[]> classBytes = new HashMap<String, byte[]>();

    public CachingGroovyClassLoader(){

    }
    public CachingGroovyClassLoader(ClassLoader parent){
        super(parent);
    }

    public byte[] getClassBytes(String name) throws IOException{
        return IOUtils.toByteArray(getResourceAsStream(name));
    }

    @Override
    public InputStream getResourceAsStream(String name) {
        if (classBytes.containsKey(name)) {
            return new ByteArrayInputStream(classBytes.get(name));
        }
        return super.getResourceAsStream(name);
    }

    @Override
    protected ClassCollector createCollector(CompilationUnit unit, SourceUnit su) {
        // These six lines copied from Groovy itself, with the intention to
        // return a subclass
        InnerLoader loader = AccessController.doPrivileged(new PrivilegedAction<InnerLoader>() {
            public InnerLoader run() {
                return new InnerLoader(CachingGroovyClassLoader.this);
            }
        });
        return new BytecodeClassCollector(classBytes, loader, unit, su);
    }

    public static class BytecodeClassCollector extends ClassCollector {
        private final Map<String, byte[]> classBytes;

        public BytecodeClassCollector(Map<String, byte[]> classBytes, InnerLoader loader, CompilationUnit unit,
                SourceUnit su) {
            super(loader, unit, su);
            this.classBytes = classBytes;
        }

        @Override
        protected Class<?> onClassNode(ClassWriter classWriter, ClassNode classNode) {            
            classBytes.put(classNode.getName() + ".class", classWriter.toByteArray());
            return super.onClassNode(classWriter, classNode);
        }
    }

}

编辑:

我有一个发布原始类文件的请求,但我不知道该怎么做。这里 还有一些可能有用的东西:

javap ScriptTestClass.class的结果:

Compiled from "ScriptTestClass.groovy"
public class ScriptTestClass implements groovy.lang.GroovyObject {
  public static transient boolean __$stMC;
  public static long __timeStamp;
  public static long __timeStamp__239_neverHappen1417366615662;
  public ScriptTestClass();
  public void test_method(java.lang.String, java.lang.String, java.lang.String);
  public java.lang.Object this$dist$invoke$1(java.lang.String, java.lang.Object);
  public void this$dist$set$1(java.lang.String, java.lang.Object);
  public java.lang.Object this$dist$get$1(java.lang.String);
  protected groovy.lang.MetaClass $getStaticMetaClass();
  public groovy.lang.MetaClass getMetaClass();
  public void setMetaClass(groovy.lang.MetaClass);
  public java.lang.Object invokeMethod(java.lang.String, java.lang.Object);
  public java.lang.Object getProperty(java.lang.String);
  public void setProperty(java.lang.String, java.lang.Object);
  public static void __$swapInit();
  static {};
  public void super$1$wait();
  public java.lang.String super$1$toString();
  public void super$1$wait(long);
  public void super$1$wait(long, int);
  public void super$1$notify();
  public void super$1$notifyAll();
  public java.lang.Class super$1$getClass();
  public java.lang.Object super$1$clone();
  public boolean super$1$equals(java.lang.Object);
  public int super$1$hashCode();
  public void super$1$finalize();
  static java.lang.Class class$(java.lang.String);
}

1 个答案:

答案 0 :(得分:0)

我能够在Groovy-dev列表的帮助下回答这个问题。

简而言之,它们存在,因为还有其他用例需要它们,但在这种情况下不需要它们,并且在静态情况下删除它们的努力是令人望而却步的。

确切的答案(感谢Cedric Champeau on groovy-dev):

  

现在答案很简单:这是公共基础设施的工件   我们有动态和静态字节码。   即使你看到的代码不是最理想的,也没有错。在   Groovy,每个表达式都有一个返回值,所以我们需要一个本地   变量(这里是插槽4)来存储它。这个返回值是这样的事实   未使用的是编译器分析的内容(两者都有)   动态和静态模式),因此不能优化不使用本地   变量。做到这一点会更好,因为它会减少   字节码的大小,使它看起来更像Java,因此制作它   更可能是JIT(因为字节码的大小是   减少了,因为它可以看起来更接近于被认可的模式   JIT),但这样做很难。