13x performance increase in jar comparison by parsing method signatures

New class to parse and replace method signatures, instead of iterating over
each class. On my machine, old technique takes 6.8 to compare Minecraft 1.4.7,
new technique: 0.5 seconds, with identical results.
This commit is contained in:
Agaricus 2013-01-22 22:15:13 -08:00
parent 97d8e80769
commit 3ff134311d
3 changed files with 104 additions and 32 deletions

View File

@ -29,7 +29,6 @@
package net.md_5.specialsource; package net.md_5.specialsource;
import java.io.*; import java.io.*;
import java.text.MessageFormat;
import java.util.*; import java.util.*;
public class JarMapping { public class JarMapping {
@ -79,7 +78,7 @@ public class JarMapping {
} else if (tokens.length == 4) { } else if (tokens.length == 4) {
String oldClassName = shader.shade(tokens[0]); String oldClassName = shader.shade(tokens[0]);
String oldMethodName = tokens[1]; String oldMethodName = tokens[1];
String oldMethodDescriptor = tokens[2]; String oldMethodDescriptor = tokens[2]; // TODO shader.shadeMethodSignature(tokens[2]);
String newMethodName = tokens[3]; String newMethodName = tokens[3];
methods.put(oldClassName + "/" + oldMethodName + " " + oldMethodDescriptor, newMethodName); methods.put(oldClassName + "/" + oldMethodName + " " + oldMethodDescriptor, newMethodName);
} }
@ -87,14 +86,6 @@ public class JarMapping {
} }
} }
private static String shade(String className, ShadeRelocationSimulator shadeRelocationSimulator) {
if (shadeRelocationSimulator == null) {
return className;
}
return shadeRelocationSimulator.shade(className);
}
/** /**
* Generate a mapping given an original jar and renamed jar * Generate a mapping given an original jar and renamed jar
* @param oldJar Original jar * @param oldJar Original jar
@ -144,10 +135,8 @@ public class JarMapping {
String key = oldMethod.owner + "/" + oldMethod.name + " " + oldMethod.descriptor; String key = oldMethod.owner + "/" + oldMethod.name + " " + oldMethod.descriptor;
methods.put(key, newMethod.name); methods.put(key, newMethod.name);
String oldDescriptor = oldMethod.descriptor; MethodDescriptorTransformer methodDescriptorTransformer = new MethodDescriptorTransformer(null, classes);
for (Map.Entry<String, String> entry : classes.entrySet()) { String oldDescriptor = methodDescriptorTransformer.transform(oldMethod.descriptor);
oldDescriptor = oldDescriptor.replaceAll("L" + entry.getKey() + ";", "L" + entry.getValue() + ";"); // TODO: efficiency
}
if (!Objects.equals(oldMethod.name + " " + oldDescriptor, newMethod.name + " " + newMethod.descriptor)) { if (!Objects.equals(oldMethod.name + " " + oldDescriptor, newMethod.name + " " + newMethod.descriptor)) {
srgWriter.addMethodMap(oldMethod, newMethod); srgWriter.addMethodMap(oldMethod, newMethod);

View File

@ -33,16 +33,9 @@ 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.io.PrintWriter;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.jar.JarEntry; import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream; import java.util.jar.JarOutputStream;
import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassReader;
@ -64,23 +57,34 @@ public class JarRemapper extends Remapper {
@Override @Override
public String map(String typeName) { public String map(String typeName) {
return mapTypeName(typeName, jarMapping.packages, jarMapping.classes);
}
public static String mapTypeName(String typeName, Map<String, String> packageMap, Map<String, String> classMap) {
int index = typeName.indexOf('$'); int index = typeName.indexOf('$');
String key = (index == -1) ? typeName : typeName.substring(0, index); String key = (index == -1) ? typeName : typeName.substring(0, index);
String mapped = null; String mapped = mapClassName(typeName, packageMap, classMap);
for (String oldPackage : jarMapping.packages.keySet()) {
if (key.startsWith(oldPackage)) {
String newPackage = jarMapping.packages.get(oldPackage);
mapped = newPackage + key.substring(oldPackage.length());
break;
}
}
if (mapped == null) {
mapped = jarMapping.classes.get(key);
}
return mapped != null ? mapped + (index == -1 ? "" : typeName.substring(index, typeName.length())) : typeName; return mapped != null ? mapped + (index == -1 ? "" : typeName.substring(index, typeName.length())) : typeName;
} }
/**
* Helper method to map a class name by package (prefix) or class (exact) map
*/
private static String mapClassName(String className, Map<String, String> packageMap, Map<String, String> classMap) {
if (packageMap != null) {
for (String oldPackage : packageMap.keySet()) {
if (className.startsWith(oldPackage)) {
String newPackage = packageMap.get(oldPackage);
return newPackage + className.substring(oldPackage.length());
}
}
}
return classMap != null ? classMap.get(className) : null;
}
@Override @Override
public String mapFieldName(String owner, String name, String desc) { public String mapFieldName(String owner, String name, String desc) {
String mapped = tryClimb(jarMapping.fields, NodeType.FIELD, owner, name); String mapped = tryClimb(jarMapping.fields, NodeType.FIELD, owner, name);

View File

@ -0,0 +1,79 @@
package net.md_5.specialsource;
import java.util.Map;
public class MethodDescriptorTransformer {
private Map<String, String> packageMap;
private Map<String, String> classMap;
public MethodDescriptorTransformer(Map<String, String> packageMap, Map<String, String> classMap) {
this.packageMap = packageMap;
this.classMap = classMap;
}
public String transform(String input) {
StringBuilder output = new StringBuilder();
int i = 0;
while(i < input.length()) {
char c = input.charAt(i);
switch(c)
{
// class
case 'L':
String rest = input.substring(i);
int end = rest.indexOf(';');
if (end == -1) {
throw new IllegalArgumentException("Invalid method descriptor, found L but missing ;: " + input);
}
String className = rest.substring(1, end);
i += className.length() + 1;
String newClassName = JarRemapper.mapTypeName(className, packageMap, classMap);
output.append("L" + newClassName + ";");
break;
// primitive type
case 'B':
case 'C':
case 'D':
case 'F':
case 'I':
case 'J':
case 'S':
case 'V':
case 'Z':
// arguments
case '(':
case ')':
// array
case '[':
output.append(c);
break;
case 'T':
throw new IllegalArgumentException("Method descriptors with type variables unsupported: "+c);
case '<':
throw new IllegalArgumentException("Method descriptors with optional arguments unsupported: "+c);
case '*':
case '+':
case '-':
throw new IllegalArgumentException("Method descriptors with wildcards unsupported: "+c);
case '!':
case '|':
case 'Q':
throw new IllegalArgumentException("Method descriptors with advanced types unsupported: "+c);
default:
throw new IllegalArgumentException("Unrecognized type in method descriptor: " + c);
}
i += 1;
}
return output.toString();
}
}