Side steps
Groovy docs look unfriendly in that they show us the generated intermediate Java class examples, like [groovy_site, exact point], but give us no easy way to see the same about our own Groovy scripts. Here are some possible reasons:
groovy.lang.Script
and puts
our code inside the run()
method, but doesn't keep source artifacts unless asked to —
and only partially (e.g., in joint compilation)scalac -Xprint:typer
)
or Kotlin (-Xdump-declarations
), Groovy lacks an official --emit-java
or
--emit-script-class
flag. That makes it harder to introspectHere's the workaround we are going to use:
groovyc myscript.groovy
myscript.class
filedel myscript.class
cfr-0.152.jar
file that is ready to useHere's a short example. Groovy generated class file methods.class
is going to be decompiled:
>set CFR_HOME=directory\where\cfr-0.152.jar\is_located\
>java -jar %CFR_HOME%cfr-0.152.jar methods.class > methods.java
> del methods.class
Here are the most interesting fragments of the result:
/*
* Decompiled with CFR 0.152.
*
* Could not load the following classes:
* groovy.lang.Binding
* groovy.lang.MetaClass
* groovy.lang.Script
...
*/
import groovy.lang.Binding;
import groovy.lang.MetaClass;
import groovy.lang.Script;
...
public class methods
extends Script {
...
public methods() {
}
public methods(Binding context) {
super(context);
}
public static void main(String ... args) {
IndyInterface.bootstrap("invoke", "runScript", 0, InvokerHelper.class, methods.class, args);
}
public Object run() {
IndyInterface.bootstrap("invoke", "println", 2, this, IndyInterface.bootstrap("invoke", "sum", 2, this, 7, 4));
return IndyInterface.bootstrap("invoke", "println", 2, this, IndyInterface.bootstrap("invoke", "sum", 2, this, 7));
}
public Object sum(int a, int b) {
return IndyInterface.bootstrap("invoke", "plus", 0, a, b);
}
...
}
Our script may create multiple classes, like with closures:
methods.class
methods$_run_closure1.class
In such cases we may want to decompile all of them in one go:
java -jar cfr-0.152.jar *.class
See the output of:
java -jar %CFR_HOME%cfr-0.152.jar --help
groovyc --help
to know how to specify the input and output directories for better code organization.
Also see the following options to control the decompilation process:
--hidebridgemethods true --innerclasses false --decodelambdas false --comments false
Here are explanations given by Chat GPT [chat_gpt]:
--hidebridgemethods
— suppresses synthetic bridge methods (Java/Groovy creates these for
type compatibility)--innerclasses
— prevents CFR from showing nested/inner classes inline in the parent class--decodelambdas
— avoids reconstructing lambdas and closures as if they were written inline
(useful if Groovy uses invokedynamic
)--comments
— suppresses extra helpful comments added by CFR, so it looks cleanerAs it's shown above Groovy compiler can compile a normal Java class file.
Starting from Groovy 3.0+, compiled script classes include a generated
public static void main(String... args)
method, so it may be executed by a Java runtime.
Still Groovy runtime JARs (groovy-*.jar
) need to be added to the class path:
>type hello_world.groovy
println "Hello world!"
>groovyc hello_world.groovy
>dir /b | find "hello_world.class"
hello_world.class
>java -cp ".;%GROOVY_HOME%\lib\*" hello_world
Hello world!
>del hello_world.class
The GROOVY_HOME
environment variable should point to the Groovy installation directory (or the
certain path may be used).