I'm seeing some strange results when i view the bytecode for a Groovy Script that was compiled with @groovy.transform.CompileStatic
Here is the simplest class that duplicates the problem:
@groovy.transform.CompileStatic
class ScriptTestClass{
void test_method(String x,String y,String z){
x = "foo";
}
}
When compiled to bytecode, i get this bytecode ( results of javap -c -v ScriptTestClass.class, edited for the subject method only ):
Results of javap -c -v ScriptTestClass.class ( just the subject method though ):
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
Clearly, ASTORE / ALOAD 4 are not appropriate here. In fact, they appear extraneous. The bytecode is correct if these are removed. The correct bytecode ( which I get when i write this same code in a Java class is:
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;
Why does @CompileStatic produce the bytecodes accessing local variable slot 4?
I'm using d Groovy version 2.3.7, and Java 1.7
In case it is relevant, the code I'm using to get bytes from the Groovy source is below.
This code parses a script as a class and gets the bytes:
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' above is an instance of the following classloader, which allows fetching the bytes after loading:
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);
}
}
}
EDIT:
I had a request to post the raw class files, but i dont know how to do that here. Here are a couple more things that might be helpful:
results of 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);
}