java-bytecode-asm

Method not found in class using ASM byte code


I have a perplexing problem. We use run-time created codec:s for message en- and decoding. The original authors has long since left the building. I've been asked to add some rather simple functionality to the blink format codecs. We want to pass delta values (just send changed fields instead of the entire message but you are also supposed to be able to null fields, leading to some kind of trinary logic). I've written a class Patchable that has a get and set for the payload. If the payload is null, null the actual field. I've added the ASM for both the encoding and the decoding and it worked for a short while but now I get a Method not found error when I try to get the payload in the encoding stage.

Here is the Patchable class:

public class Patchable<T> {
    public Patchable() {}

    public static <T> Patchable<T> of(T value) {
        return new Patchable<>(value);
    }

    public Patchable(final T pPayload) {
        payload = pPayload;
    }

    private T payload;

    public boolean isExplicitlyNull(){return payload == null;}

    public T getPayload(){return payload;}

    public void setPayload(T payload) {
        this.payload = payload;
    }
}

Here's an example of a carrier class:

public class PatchableEntity extends MsgObject {
    @PropertyType(String.class)
    private Patchable<String> patchable;

    public Patchable<String> getPatchable() {
        return patchable;
    }

    public void setPatchable(Patchable<String> patchable) {
        this.patchable = patchable;
    }
}

The Property annotation is for indicating the class carried by the Patchable instance.

My test code is :

    public void testPatch2() throws IOException {
        Schema schema = new SchemaBuilder().build(PatchableEntity.class);

        BlinkCodec codec = new BlinkCodecFactory(schema).createCodec();
        var pe = new PatchableEntity();
        var patchable = new Patchable<String>("payload");
        pe.setPatchable(patchable);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        codec.encode(pe, out);
    }

So, this fails with

`'java.lang.String com.cinnober.msgcodec.Patchable.getPayload()'

java.lang.NoSuchMethodError: 'java.lang.String com.cinnober.msgcodec.Patchable.getPayload()' `

The generated byte code I get from ASM is :

 private writeStaticGroup_PatchableEntity(Lcom/cinnober/msgcodec/io/ByteSink;Lcom/cinnober/msgcodec/blink/PatchableEntity;)V throws java/io/IOException
    ALOAD 1
    ALOAD 0
    GETFIELD com/cinnober/msgcodec/blink/GeneratedBlinkCodec0.accessor_PatchableEntity_patchable : Lcom/cinnober/msgcodec/Accessor;
    ALOAD 2
    INVOKEINTERFACE com/cinnober/msgcodec/Accessor.getValue (Ljava/lang/Object;)Ljava/lang/Object; (itf)
    CHECKCAST com/cinnober/msgcodec/Patchable
    INVOKEVIRTUAL com/cinnober/msgcodec/Patchable.getPayload ()Ljava/lang/String;
    INVOKESTATIC com/cinnober/msgcodec/blink/BlinkOutput.writeStringUTF8Null (Lcom/cinnober/msgcodec/io/ByteSink;Ljava/lang/String;)V
    RETURN
    MAXSTACK = 4
    MAXLOCALS = 3

The CHECKCAST instruction is successful, otherwise we wouldn't have gotten past there. So, why can't the codec see the getPayload() method? It's a mystery to me!


Solution

  • And I found the answer!

    My ASM code looked like this :

    mv.visitMethodInsn(INVOKEVIRTUAL, "com/cinnober/msgcodec/Patchable", "getPayload","()L<java/lang/String>;" , false);
    

    (the java/lang/String comes from the generic type that I've resolved earlier. )

    Changing it to :

    mv.visitMethodInsn(INVOKEVIRTUAL, "com/cinnober/msgcodec/Patchable", "getPayload","()Ljava/lang/Object;" , false);
    mv.visitTypeInsn(CHECKCAST,  Type.getInternalName(componentJavaClass) );
    

    where componentJavaClass is the resolved generic type.

    So, the CHECKCAST is needed when working with generics.

    I did what I should've done from the start, I wrote some small code that exhibited the behavior I wanted and decompiled it.