Search code examples
javafieldbytecodejavassist

Get index of declared field in class using Javassist for use in bytecode


I want to use bytecode instrumentation to access an integer array field, that I've added to a class. To do that, I need the index of the field in the class file, but I can't find it.

To explain in more detail, instead of (desired is a declared local integer, whose index I've grabbed from the local variable table):

desired = Server.rand.nextInt(4);

I want to do:

desired = _MyField[Server.rand.nextInt(_MyField.length)];

In JASM that would be:

// field reference to later access an index in the array
aload <local variable index of "this"> (push reference of class)
getfield <fieldindex> (pop reference, push field reference to stack)

// rand is a public static field in class Server
getstatic java.util.Random com.wurmonline.server.Server.rand

// field reference to pass to arraylength and the Random.nextInt(int) call
aload <local variable index of "this">
getfield <fieldindex> (push reference of array)

arraylength (pop reference, push array.length value)
invokevirtual int java.util.Random.nextInt(int) (pop length for rng, push index)
iaload (pop index, push integer value from array index)
istore <index of local variable "desired"> (pop value, store in variable)

The field is added using Javassist, were sb is a StringBuilder containing the field initialiser (e.g. "new int[] { n, n, n, n, n };"):

ctClass.addField(
    new CtField(
            HookManager.getInstance().getClassPool().get("[I"),
            "_OtherServerMissions", 
            ctClass),
    sb.toString());

Looking at a class file using DirtyJOE, a byte code editor that works with Java8/Major class file version 52, I can't find an index value for fields. Nor can I seem to find one using CtField, or an applicable AttributeInfo.

for (CtField ctField : ctClass.getDeclaredFields())

So I'm left wondering where to get the fieldindex from.

I hope the explanation was clear enough for the poor souls familiar with the matter.


Solution

  • Sometimes it really helps writing out the problem in a way to explain it to others.

    The FIELDINDEX passed to the GETFIELD opcode is a reference into the constant pool of the class. You will have to add a FieldrefInfo to the class' constant pool. The return value of addFieldrefInfo() is its index in the constant pool.

    ConstPool constPool = ctClass.getClassFile().getConstPool();
    
    [...]
    
    sb = new StringBuilder();
    sb.append("new int[] { ");
    for (int i : _OtherServerMissions)
        sb.append(String.format("%d,", i));
    sb.deleteCharAt(sb.lastIndexOf(","));
    sb.append(" };");
    
    ctClass.addField(
            new CtField(
                    HookManager.getInstance().getClassPool().get("[I"),
                    "_OtherServerMissions", 
                    ctClass),
            sb.toString());
    
    int otherFieldIndex = constPool.addFieldrefInfo(constPool.getThisClassInfo(), "_OtherServerMissions", "[I");
    logger.log(Level.INFO, "Added OtherFieldIndex as " + otherFieldIndex);
    

    You will also have to determine the index of "this", to push a reference of the class instance (if applicable) on the stack before using GETFIELD.

    int thisIndex = -1;
    LocalVariableAttribute lva = (LocalVariableAttribute)generateMission
        .getMethodInfo()
        .getCodeAttribute()
        .getAttribute(LocalVariableAttribute.tag);
    
    for (int i = 0; thisIndex < 0 && i < lva.tableLength(); i++) {
        if (lva.variableName(i).equals("this"))
            thisIndex = lva.index(i);
    }
    

    If it's not in the local variable table already, I don't know how to add it at this point. Or how exactly to add to the local variable table in general. Feel free to leave a comment (if you have enough rep points, duh...) if you do know.

    The fixed question now contains the correct java assembly code used with the values outlined here.