Groovy: How-to decompile Groovy classes

By | November 2, 2013

So you want to get back your Groovy based scripts from Java byte-code (classes).

Well I’m afraid this ticket isn’t for you! After a lot of searching I found out that no specific Groovy decompiler exists (for the moment). But don’t panic, the method I’ll give you just after will help you recover the algorithm used.

The story (True story):

Our client chose a company to create a small web-application based on Grails (which takes advantage of Groovy), unfortunately the company shut down and our client could only retrieve the compiled classes (packed in the WAR). No source code available…
Worst comes to worst, our client didn’t have any document describing what was the server actually doing !

The client asked for an expertise to see if getting back the knowledge about what the server was made for could be done. I’m no Groovy user but hey it’s based on Java… I was sure I could do something. After some effort, I was able to get back the algorithms used in the different Groovy controllers methods. Here is the trick:

From Groovy classes to Algorithm

Lets take a sample projet: book-flow

Decompiling the classes

I did use two decompilers to do the job (You’ll understand right-away why I did use two different ones):

First I went with JAD which I’m used to, it decompiled easily the classes but then I encountered a problem : Groovy closures.
If you take a look at the generated classes then the simple controller from the sample : BookController will generate 19 classes (Strictly speaking, 1 class and 18 inner classes)!

The problem with JAD is that it’ll decompile a single BookControllerClass.java with everything inside which is almost unreadable as a closure can contain multiple closures itself etc… That’s why I went for JD-GUI that allows to decompile inner classes (closure) independently (But JAD isn’t dead yet as you’ll see).

Find the controller method and associated closure

Let’s take a look at a piece of the decompiled class BookController.class :

BookController._closure1 local_closure1 = new BookController._closure1(this, this);
this.test = local_closure1;
BookController._closure2 local_closure2 = new BookController._closure2(this, this);
this.index = local_closure2;
BookController._closure3 local_closure3 = new BookController._closure3(this, this);
this.shoppingCartFlow = local_closure3;

From here we can guess we got the three entry points that would be book/test, book/index and book/shoppingCartFlow.

Looking into the closure

Let’s take a look into shoppingCartFlow which code will be in _closure3 class file (BookController$_closure3.class) with JD-GUI

  public Object doCall(Object it)
  {
    CallSite[] arrayOfCallSite = $getCallSiteArray(); arrayOfCallSite[0].callCurrent(this, new BookController._closure3_closure4(this, getThisObject()));
    arrayOfCallSite[1].callCurrent(this, new BookController._closure3_closure5(this, getThisObject()));
    arrayOfCallSite[2].callCurrent(this, new BookController._closure3_closure6(this, getThisObject()));
    arrayOfCallSite[3].callCurrent(this, new BookController._closure3_closure7(this, getThisObject()));
    arrayOfCallSite[4].callCurrent(this, new BookController._closure3_closure8(this, getThisObject()));
    arrayOfCallSite[5].callCurrent(this, new BookController._closure3_closure9(this, getThisObject()));
    arrayOfCallSite[6].callCurrent(this, new BookController._closure3_closure10(this, getThisObject()));
    arrayOfCallSite[7].callCurrent(this, new BookController._closure3_closure11(this, getThisObject()));
    arrayOfCallSite[8].callCurrent(this);
    return arrayOfCallSite[9].callCurrent(this);
  }

You’ll find the doCall(Object it) method in every Closure and from what I’ve seen you can consider that this is THE main method.
So here comes the interesting part, you’ll find a lot arrayOfCallSite[] call, but with JD-GUI there is nowhere to find out how this array is filled.
Decompiling with JAD will give you as said only one file, but you’ll find the initialization for every closure arrayOfCallSite. Here is what can be found for the _closure3 class :

as[0] = "getBooks";
as[1] = "showCatalogue";
as[2] = "showCart";
as[3] = "enterPersonalDetails";
as[4] = "enterShipping";
as[5] = "enterPayment";
as[6] = "confirmPurchase";
as[7] = "processPurchaseOrder";
as[8] = "displayInvoice";
as[9] = "handleError";

From decompiled Java to algorithm

And now here comes the “magic”:
The values in the array are the methods that will be called against the first argument in the callCurrent method, other arguments will be passed to the method.

Let’s focus on one method for this post:
processPurchaseOrder

Here is the Groovy code so you can compare our finding with the original source code :

processPurchaseOrder {
  action {
    def a = flow.address
    def p = flow.person
    def pd = flow.paymentDetails
    def cartItems = flow.cartItems
    def o = new Order(person:p, shippingAddress:a, paymentDetails:pd)
    o.invoiceNumber = new Random().nextInt(9999999)                                                                
    cartItems.each { o.addToItems(it) }
    [order:o]
  }
  on("error").to "confirmPurchase"
  on(Exception).to "confirmPurchase"
  on("success").to "displayInvoice"
}

Looking back at our decompiled code :

    arrayOfCallSite[7].callCurrent(this, new BookController._closure3_closure11(this, getThisObject()));

can be “converted” to:

Call “processPurchaseOrder” with argument _closure3_closure11

So next step is to look in the BookController$_closure3_closure11 class (JD-GUI):

public Object doCall(Object it)
  {
    CallSite[] arrayOfCallSite = $getCallSiteArray();
    arrayOfCallSite[0].callCurrent(this, new BookController._closure3_closure11_closure17(this, getThisObject()));
 
    arrayOfCallSite[1].call(arrayOfCallSite[2].callCurrent(this, "error"), "confirmPurchase");
    arrayOfCallSite[3].call(arrayOfCallSite[4].callCurrent(this, Exception.class), "confirmPurchase");
    return arrayOfCallSite[5].call(arrayOfCallSite[6].callCurrent(this, "success"), "displayInvoice");
  }

And the values of the array using JAD :

as[0] = "action";
as[1] = "to";
as[2] = "on";
as[3] = "to";
as[4] = "on";
as[5] = "to";
as[6] = "on";

Let’s “convert” it :

this.action(new _closure3_closure11_closure17());

this.on(“error”).to(“confirmPurchase”);
this.on(Exception.class).to(“confirmPurchase”);
return this.on(“success”).to(“displayInvoice”);

The three last line are close enough to the Groovy code and as a non-groovy developer, I’m guessing these are redirections. The first line again instantiate a closure that we need to convert.
Let’s go with : BookController$_closure3_closure11_closure17.class

Object a = arrayOfCallSite[0].callGetProperty(arrayOfCallSite[1].callGroovyObjectGetProperty(this));
Object p = arrayOfCallSite[2].callGetProperty(arrayOfCallSite[3].callGroovyObjectGetProperty(this));
Object pd = arrayOfCallSite[4].callGetProperty(arrayOfCallSite[5].callGroovyObjectGetProperty(this));
Object cartItems = arrayOfCallSite[6].callGetProperty(arrayOfCallSite[7].callGroovyObjectGetProperty(this));
Reference o = new Reference(arrayOfCallSite[8].callConstructor(Order.class, ScriptBytecodeAdapter.createMap(new Object[] { "person", p, "shippingAddress", a, "paymentDetails", pd })));
Object localObject1 = arrayOfCallSite[9].call(arrayOfCallSite[10].callConstructor(Random.class), Integer.valueOf(9999999)); ScriptBytecodeAdapter.setProperty(localObject1, null, o.get(), "invoiceNumber");
arrayOfCallSite[11].call(cartItems, new BookController._closure3_closure11_closure17_closure18(this, getThisObject(), o));
return ScriptBytecodeAdapter.createMap(new Object[] { "order", o.get() });

Note this time the two different methods:
* callGroovyObjectGetProperty –> get a property (variable) from a Groovy Object (We convert to direct variable call)
* callGetProperty –> get a property (We convert to getVariable() )

as[0] = "address";
as[1] = "flow";
as[2] = "person";
as[3] = "flow";
as[4] = "paymentDetails";
as[5] = "flow";
as[6] = "cartItems";
as[7] = "flow";
as[8] = "<$constructor$>";
as[9] = "nextInt";
as[10] = "<$constructor$>";
as[11] = "each";

Conversion:

Object a = flow.getAddress();
Object p = flow.getPerson();
Object pd = flow.getPaymentDetails();
Object cartItems = flow.getCartItems;
Reference o = new Reference(new Order(new Map(“person”=> p, “shippingAddress”=> a, “paymentDetails”=> pd)));
Object localObject1 = new Random(9999999).nextInt();
o.setInvoiceNumber(localObject1);
for each (cartItems) {o.addItems(it)}
return new Map(“order”=>o);

Yeah you did notice I didn’t bother looking into the BookController._closure3_closure11_closure17_closure18 closure. The code in there is quite simple: one line that you’ll convert yourself ;) :

return arrayOfCallSite[0].call(this.o.get(), it);
as[0] = "addToItems";

 Conclusion

Here the “converted” code really look like the Groovy source, but you can’t rely on that.

The best we can guess is the global algorithm of the methods, which is sufficient if your objective is to rewrite the server code. If you were looking for a straight conversion then you’ll have to create your own decompiler/converter.

Thanks for reading.

4 thoughts on “Groovy: How-to decompile Groovy classes

  1. Pingback: Gilbert

  2. Lavonne

    I like the helpful information you provide in your articles.
    I’ll bookmark your blog and check again here frequently.
    I’m quite certain I’ll learn many new stuff right here!
    Best of luck for the next!

    Reply
  3. Johnny

    We just need to wrap this up in an application that automates it now!

    Reply
  4. Michael Laffargue

    Thanks for reading.
    The theme is free and can be found almost on the first page of WordPress downloadable theme.

    Hope this ticket helped you somehow.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *