2017-07-20

Reference to a static method + instrumentation = fail!

In a Java project I am using ActiveJDBC ORM. Part of the ORM is an instrumentation process that adds new methods into compiled classes. In my case the instrumentation adds a few static factory methods into my Record class. For example Record.findAll() to get all records from the database.

I have an execute method that accepts a single parameter of type Callable. The method opens a connection, executes what's in the parameter and then closes it.

I implemented this a simple way. In a repository class, I just passed a reference to a static method of instrumented class into the execute method. After building the application in IntelliJ I got well known failed to determine Model class name, are you sure models have been instrumented? exception.

Even though, the model class was instrumented it didn't work. Then I made few simple changes in a repository class, not related to the execute method itself, and it started working. I was confused.

After a disassembly of repository class I noticed that there is a reference to Record's parent class instead of Record itself. Making small changes in repository and recompilation, fixed the reference.

Reason

  • First compilation went this way:

    1. Java compiled clean project (all classes were compiled).

      First, it compiled model class. The class was not instrumented so there were no static methods at this time.

      Then, it compiled repository class. Because model did not have any static methods at that time, a reference to a static method in the model class was "pointed" to a parent class method.

    2. ActiveJDBC plugin instrumented the model class but reference from repository to model's parent was already set, so it didn't make any change.

    At this moment, the bytecode of static method call looked like this:

    Method arguments:
      #33 ()Ljava/lang/Object;
      #34 invokestatic org/javalite/activejdbc/Model.findAll:()Lorg/javalite/activejdbc/LazyList;
      #35 ()Ljava/util/List;
    
  • Second compilation, after small changes in repository:

    1. Java compiled only the changed repository class. That means at this moment model class had static methods so the reference was set correctly.

    2. ActiveJDBC plugin instrumented the model class but that was not important at the moment.

    Now, the bytecode is correct:

    Method arguments:
      #33 ()Ljava/lang/Object;
      #34 invokestatic demo/Record.findAll:()Lorg/javalite/activejdbc/LazyList;
      #35 ()Ljava/util/List;
    

How to avoid this

Instead of reference to a static method use lambda method call.

So instead of this:

public List<Record> findAll() throws Exception {
    return execute(Record::findAll);
}

write this:

public List<Record> findAll() throws Exception {
    return execute(() -> Record.findAll());
}

In this case Java will not create a reference to parent class Model.findAll() but will point correctly to Record.findAll().