Add runtime class inheritance remapping

If -l is specified, will now traverse the runtime class hierarchy in
tryClimb to determine the method/symbol from the parent to remap, in
addition to the classes in the jar.

This can be used to remap plugins that call into external classes.
For example, with a Bukkit plugin:

 java -cp ../jars/craftbukkit-1.4.7-R0.1.jar:target/SpecialSource-1.1-SNAPSHOT.jar net.md_5.specialsource.SpecialSource --shade-relocation net.minecraft.server=net.minecraft.server.v1_4_R1 --shade-relocation org.bouncycastle=net.minecraft.v1_4_R1.org.bouncycastle --srg-in ../jars/1.4.7/cb2obf.csrg  --in ../IncompatiblePlugin/IncompatiblePlugin-01.jar --out /tmp/bp/out.jar

using:
 https://bitbucket.org/agaricusb/incompatibleplugin/downloads/IncompatiblePlugin-01.jar
 6d75d94496

agaricus/plugins/IncompatiblePlugin/SamplePosCommand.java will be remapped:

  worldServer.q(...)

from worldServer.getTileEntity(). World provides getTileEntity and is remapped in cb2obf;
but WorldServer subclasses World and overrides getTileEntiy.. this information is not
available in the plugin itself and would not be remapped alone. By adding the CB jar to
the classpath and enabling runtime inheritance, the call is correctly remapped.
This commit is contained in:
Agaricus 2013-01-23 00:23:38 -08:00
parent fa2089271a
commit 297b9f5a33
4 changed files with 81 additions and 11 deletions

View File

@ -5,6 +5,9 @@ import org.objectweb.asm.tree.ClassNode;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
/**
* Lookup inheritance from a class given a jar
*/
public class JarInheritanceProvider implements IInheritanceProvider { public class JarInheritanceProvider implements IInheritanceProvider {
private final Jar self; private final Jar self;
@ -14,14 +17,23 @@ public class JarInheritanceProvider implements IInheritanceProvider {
@SuppressWarnings("unchecked") // Saddens me to see ASM strip vital info like that @SuppressWarnings("unchecked") // Saddens me to see ASM strip vital info like that
public List<String> getParents(String owner) { public List<String> getParents(String owner) {
System.out.println("jar: owner "+owner);
List<String> parents = new ArrayList<String>(); List<String> parents = new ArrayList<String>();
ClassNode node = self.getNode(owner); ClassNode node = self.getNode(owner);
if (node != null) { if (node != null) {
for (String iface : (List<String>) node.interfaces) { for (String iface : (List<String>) node.interfaces) {
System.out.println("jar: add iface="+iface);
parents.add(iface); parents.add(iface);
} }
System.out.println("jar: add super="+node.superName);
parents.add(node.superName); parents.add(node.superName);
} else {
System.out.println("jar: nothing for "+owner);
} }
return parents; return parents;
} }
public String toString() {
return getClass().getSimpleName()+"("+self.file.getName()+")";
}
} }

View File

@ -33,6 +33,7 @@ import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -47,12 +48,12 @@ import org.objectweb.asm.tree.ClassNode;
public class JarRemapper extends Remapper { public class JarRemapper extends Remapper {
private static final int CLASS_LEN = ".class".length(); private static final int CLASS_LEN = ".class".length();
private final IInheritanceProvider inheritanceProvider; private final List<IInheritanceProvider> inheritanceProviders;
private final JarMapping jarMapping; private final JarMapping jarMapping;
private JarRemapper(JarMapping jarMapping, IInheritanceProvider inheritanceProvider) throws IOException { private JarRemapper(JarMapping jarMapping, List<IInheritanceProvider> inheritanceProviders) throws IOException {
this.jarMapping = jarMapping; this.jarMapping = jarMapping;
this.inheritanceProvider = inheritanceProvider; this.inheritanceProviders = inheritanceProviders;
} }
@Override @Override
@ -96,10 +97,12 @@ public class JarRemapper extends Remapper {
String mapped = map.get(key); String mapped = map.get(key);
if (mapped == null) { if (mapped == null) {
for (String parent : inheritanceProvider.getParents(owner)) { for (IInheritanceProvider inheritanceProvider : inheritanceProviders) {
mapped = tryClimb(map, type, parent, name); for (String parent : inheritanceProvider.getParents(owner)) {
if (mapped != null) { mapped = tryClimb(map, type, parent, name);
return mapped; if (mapped != null) {
return mapped;
}
} }
} }
} }
@ -112,11 +115,15 @@ public class JarRemapper extends Remapper {
return mapped == null ? name : mapped; return mapped == null ? name : mapped;
} }
public static void renameJar(Jar jar, File target, JarMapping jarMapping) throws IOException { public static void renameJar(Jar jar, File target, JarMapping jarMapping, boolean live) throws IOException {
JarOutputStream out = new JarOutputStream(new FileOutputStream(target)); JarOutputStream out = new JarOutputStream(new FileOutputStream(target));
try { try {
JarInheritanceProvider jarInheritanceProvider = new JarInheritanceProvider(jar); List<IInheritanceProvider> inheritanceProviders = new ArrayList<IInheritanceProvider>();
JarRemapper self = new JarRemapper(jarMapping, jarInheritanceProvider); inheritanceProviders.add(new JarInheritanceProvider(jar));
if (live) {
inheritanceProviders.add(new RuntimeInheritanceProvider());
}
JarRemapper self = new JarRemapper(jarMapping, inheritanceProviders);
if (jar == null) { if (jar == null) {
return; return;
} }

View File

@ -0,0 +1,49 @@
package net.md_5.specialsource;
import java.util.List;
import java.util.ArrayList;
/**
* Lookup class inheritance from classes loaded at runtime
*/
public class RuntimeInheritanceProvider implements IInheritanceProvider {
// TODO: option to transform through a jarRemapper at runtime
public List<String> getParents(String internalClassName) {
List<String> parents = new ArrayList<String>();
String sourceClassName = toSourceName(internalClassName);
Class clazz;
try {
clazz = ClassLoader.getSystemClassLoader().loadClass(sourceClassName); // load class without initializing
//clazz = Class.forName(toSourceName(sourceClassName)); // runs static initializers - avoid!
} catch (Throwable t) {
System.out.println("RuntimeInheritanceProvider failed: "+t);
return parents;
}
for (Class iface : clazz.getInterfaces()) {
parents.add(toInternalName(iface.getName()));
}
Class superClass = clazz.getSuperclass();
if (superClass != null) {
parents.add(toInternalName(superClass.getName()));
}
return parents;
}
// Convert class name from internal name to source name
public String toSourceName(String className) {
return className.replace('/', '.');
}
// .. and vice versa
public String toInternalName(String className) {
return className.replace('.', '/');
}
public String toString() {
return this.getClass().getSimpleName();
}
}

View File

@ -76,6 +76,8 @@ public class SpecialSource {
.withRequiredArg() .withRequiredArg()
.withValuesSeparatedBy(','); .withValuesSeparatedBy(',');
acceptsAll(asList("l", "live"), "Enable runtime inheritance lookup");
acceptsAll(asList("q", "quiet"), "Quiet mode"); acceptsAll(asList("q", "quiet"), "Quiet mode");
acceptsAll(asList("c", "compact"), "Output mapping file in compact format"); acceptsAll(asList("c", "compact"), "Output mapping file in compact format");
} }
@ -150,7 +152,7 @@ public class SpecialSource {
log("Remapping final jar"); log("Remapping final jar");
Jar jar3 = Jar.init((File)options.valueOf("in-jar")); Jar jar3 = Jar.init((File)options.valueOf("in-jar"));
JarRemapper.renameJar(jar3, (File)options.valueOf("out-jar"), jarMapping); JarRemapper.renameJar(jar3, (File)options.valueOf("out-jar"), jarMapping, options.has("live"));
} }
} }