Search code examples
javabytecodejava-bytecode-asm

Duplicate fields in renamed classes (ASM + Jar)


For a project I'm working on I'm trying to rename classes inside a jar file using ASM. I've got it working... but there's a catch. Each class that is renamed in the jar has it's fields duplicated.

Here's my code:

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.commons.Remapper;
import org.objectweb.asm.commons.RemappingClassAdapter;
import org.objectweb.asm.commons.SimpleRemapper;
import org.objectweb.asm.tree.ClassNode;
//Imports showing which ASM (5.0.4) classes I'm using

private static void doStuff() throws IOException {
    File jar = new File("Input.jar");
    //Get's the ClassNodes for each class in a jar
    ArrayList<ClassNode> nodes = BCU.loadClasses(jar);
    //Appends '_Test' to each class except for the main class.
    Remapper mapper = new SimpleRemapper(getRename(nodes));
    //Loop through the ClassNodes updating references to renamed classes
    for (ClassNode cn : nodes) {
         ClassReader cr = new ClassReader(BCU.getNodeBytes(cn));
         ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS); 
         RemappingClassAdapter rca = new RemappingClassAdapter(cw, mapper); 
         cr.accept(rca, ClassReader.EXPAND_FRAMES); 
         cr = new ClassReader(cw.toByteArray()); 
         cr.accept(cn,ClassReader.EXPAND_FRAMES);
    }
    //Export the nodes as a new jar file and copy over the META-INF folder
    BCU.saveAsJar(nodes, "Output.jar");
}

Example output:

public static String VERSION;
public static final ConfigFile config;
public static String VERSION;
public static final ConfigFile_Test config;

Edit (3/28/2016): Coming back after learning more about ASM here's how it should be done:

for (ClassNode cn : nodes.values()) {
    ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
    //mapper is a Remapper instance
    ClassVisitor remapper = new RemappingClassAdapter(cw, mapper);
    cn.accept(remapper);
    //out = Map<String, byte[]>
    out.put(renamed.get(cn.name).getRenamed(), cw.toByteArray());
}

Antimony was right. My original idea back when I posted this was to edit the ClassNode itself (I had a ClassNode extension that handled everything. It was pretty nasty). Since moving to the Remapper implementation things have been working much better.


Solution

  • cr.accept(cn,ClassReader.EXPAND_FRAMES);
    

    The problem is that you're calling accept on an existing ClassNode. ClassNode.visitField adds a field to the class node. So everything in the ClassNode is doubled - one copy from the existing data and one copy that you added.