/*******************************************************************************
* Copyright (c) 2015 Jeff Martin.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser General Public
* License v3.0 which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/lgpl.html
*
* Contributors:
* Jeff Martin - initial API and implementation
******************************************************************************/
package cuchaz.enigma.bytecode;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javassist.CtClass;
import javassist.bytecode.*;
import javassist.bytecode.SignatureAttribute.ArrayType;
import javassist.bytecode.SignatureAttribute.BaseType;
import javassist.bytecode.SignatureAttribute.ClassSignature;
import javassist.bytecode.SignatureAttribute.ClassType;
import javassist.bytecode.SignatureAttribute.MethodSignature;
import javassist.bytecode.SignatureAttribute.NestedClassType;
import javassist.bytecode.SignatureAttribute.ObjectType;
import javassist.bytecode.SignatureAttribute.Type;
import javassist.bytecode.SignatureAttribute.TypeArgument;
import javassist.bytecode.SignatureAttribute.TypeParameter;
import javassist.bytecode.SignatureAttribute.TypeVariable;
import cuchaz.enigma.mapping.ClassEntry;
import cuchaz.enigma.mapping.ClassNameReplacer;
import cuchaz.enigma.mapping.Translator;
public class ClassRenamer
{
private static enum SignatureType
{
Class
{
@Override
public String rename(String signature, ReplacerClassMap map)
{
return renameClassSignature(signature, map);
}
},
Field
{
@Override
public String rename(String signature, ReplacerClassMap map)
{
return renameFieldSignature(signature, map);
}
},
Method
{
@Override
public String rename(String signature, ReplacerClassMap map)
{
return renameMethodSignature(signature, map);
}
};
public abstract String rename(String signature, ReplacerClassMap map);
}
private static class ReplacerClassMap extends HashMap<String, String>
{
private static final long serialVersionUID = 317915213205066168L;
private ClassNameReplacer m_replacer;
public ReplacerClassMap(ClassNameReplacer replacer)
{
m_replacer = replacer;
}
@Override
public String get(Object obj)
{
if(obj instanceof String)
return get((String)obj);
return null;
}
public String get(String className)
{
return m_replacer.replace(className);
}
}
public static void renameClasses(CtClass c, final Translator translator)
{
renameClasses(c, new ClassNameReplacer()
{
@Override
public String replace(String className)
{
ClassEntry entry =
translator.translateEntry(new ClassEntry(className));
if(entry != null)
return entry.getName();
return null;
}
});
}
public static void moveAllClassesOutOfDefaultPackage(CtClass c,
final String newPackageName)
{
renameClasses(c, new ClassNameReplacer()
{
@Override
public String replace(String className)
{
ClassEntry entry = new ClassEntry(className);
if(entry.isInDefaultPackage())
return newPackageName + "/" + entry.getName();
return null;
}
});
}
public static void moveAllClassesIntoDefaultPackage(CtClass c,
final String oldPackageName)
{
renameClasses(c, new ClassNameReplacer()
{
@Override
public String replace(String className)
{
ClassEntry entry = new ClassEntry(className);
if(entry.getPackageName().equals(oldPackageName))
return entry.getSimpleName();
return null;
}
});
}
@SuppressWarnings("unchecked")
public static void renameClasses(CtClass c, ClassNameReplacer replacer)
{
// sadly, we can't use CtClass.renameClass() because
// SignatureAttribute.renameClass() is extremely buggy =(
ReplacerClassMap map = new ReplacerClassMap(replacer);
ClassFile classFile = c.getClassFile();
// rename the constant pool (covers ClassInfo, MethodTypeInfo, and
// NameAndTypeInfo)
ConstPool constPool = c.getClassFile().getConstPool();
constPool.renameClass(map);
// rename class attributes
renameAttributes(classFile.getAttributes(), map, SignatureType.Class);
// rename methods
for(MethodInfo methodInfo : (List<MethodInfo>)classFile.getMethods())
{
methodInfo.setDescriptor(Descriptor.rename(
methodInfo.getDescriptor(), map));
renameAttributes(methodInfo.getAttributes(), map,
SignatureType.Method);
}
// rename fields
for(FieldInfo fieldInfo : (List<FieldInfo>)classFile.getFields())
{
fieldInfo.setDescriptor(Descriptor.rename(
fieldInfo.getDescriptor(), map));
renameAttributes(fieldInfo.getAttributes(), map,
SignatureType.Field);
}
// rename the class name itself last
// NOTE: don't use the map here, because setName() calls the buggy
// SignatureAttribute.renameClass()
// we only want to replace exactly this class name
String newName = renameClassName(c.getName(), map);
if(newName != null)
c.setName(newName);
// replace simple names in the InnerClasses attribute too
InnerClassesAttribute attr =
(InnerClassesAttribute)c.getClassFile().getAttribute(
InnerClassesAttribute.tag);
if(attr != null)
for(int i = 0; i < attr.tableLength(); i++)
{
// get the inner class full name (which has already been
// translated)
ClassEntry classEntry =
new ClassEntry(Descriptor.toJvmName(attr.innerClass(i)));
if(attr.innerNameIndex(i) != 0)
// update the inner name
attr.setInnerNameIndex(i, constPool.addUtf8Info(classEntry
.getInnermostClassName()));
/*
* DEBUG
* System.out.println(String.format("\tDEOBF: %s-> ATTR: %s,%s,%s"
* , classEntry, attr.outerClass(i), attr.innerClass(i),
* attr.innerName(i)));
*/
}
}
@SuppressWarnings("unchecked")
private static void renameAttributes(List<AttributeInfo> attributes,
ReplacerClassMap map, SignatureType type)
{
try
{
// make the rename class method accessible
Method renameClassMethod =
AttributeInfo.class.getDeclaredMethod("renameClass", Map.class);
renameClassMethod.setAccessible(true);
for(AttributeInfo attribute : attributes)
if(attribute instanceof SignatureAttribute)
{
// this has to be handled specially because
// SignatureAttribute.renameClass() is buggy as hell
SignatureAttribute signatureAttribute =
(SignatureAttribute)attribute;
String newSignature =
type.rename(signatureAttribute.getSignature(), map);
if(newSignature != null)
signatureAttribute.setSignature(newSignature);
}else if(attribute instanceof CodeAttribute)
{
// code attributes have signature attributes too
// (indirectly)
CodeAttribute codeAttribute = (CodeAttribute)attribute;
renameAttributes(codeAttribute.getAttributes(), map, type);
}else if(attribute instanceof LocalVariableTypeAttribute)
{
// lvt attributes have signature attributes too
LocalVariableTypeAttribute localVariableAttribute =
(LocalVariableTypeAttribute)attribute;
renameLocalVariableTypeAttribute(localVariableAttribute,
map);
}else
renameClassMethod.invoke(attribute, map);
}catch(NoSuchMethodException | IllegalAccessException
| IllegalArgumentException | InvocationTargetException ex)
{
throw new Error("Unable to call javassist methods by reflection!",
ex);
}
}
private static void renameLocalVariableTypeAttribute(
LocalVariableTypeAttribute attribute, ReplacerClassMap map)
{
// adapted from LocalVariableAttribute.renameClass()
ConstPool cp = attribute.getConstPool();
int n = attribute.tableLength();
byte[] info = attribute.get();
for(int i = 0; i < n; ++i)
{
int pos = i * 10 + 2;
int index = ByteArray.readU16bit(info, pos + 6);
if(index != 0)
{
String signature = cp.getUtf8Info(index);
String newSignature =
renameLocalVariableSignature(signature, map);
if(newSignature != null)
ByteArray.write16bit(cp.addUtf8Info(newSignature), info,
pos + 6);
}
}
}
private static String renameLocalVariableSignature(String signature,
ReplacerClassMap map)
{
// for some reason, signatures with . in them don't count as field
// signatures
// looks like anonymous classes delimit with . in stead of $
// convert the . to $, but keep track of how many we replace
// we need to put them back after we translate
int start = signature.lastIndexOf('$') + 1;
int numConverted = 0;
StringBuilder buf = new StringBuilder(signature);
for(int i = buf.length() - 1; i >= start; i--)
{
char c = buf.charAt(i);
if(c == '.')
{
buf.setCharAt(i, '$');
numConverted++;
}
}
signature = buf.toString();
// translate
String newSignature = renameFieldSignature(signature, map);
if(newSignature != null)
{
// put the delimiters back
buf = new StringBuilder(newSignature);
for(int i = buf.length() - 1; i >= 0 && numConverted > 0; i--)
{
char c = buf.charAt(i);
if(c == '$')
{
buf.setCharAt(i, '.');
numConverted--;
}
}
assert numConverted == 0;
newSignature = buf.toString();
return newSignature;
}
return null;
}
private static String renameClassSignature(String signature,
ReplacerClassMap map)
{
try
{
ClassSignature type =
renameType(SignatureAttribute.toClassSignature(signature), map);
if(type != null)
return type.encode();
return null;
}catch(BadBytecode ex)
{
throw new Error("Can't parse field signature: " + signature);
}
}
private static String renameFieldSignature(String signature,
ReplacerClassMap map)
{
try
{
ObjectType type =
renameType(SignatureAttribute.toFieldSignature(signature), map);
if(type != null)
return type.encode();
return null;
}catch(BadBytecode ex)
{
throw new Error("Can't parse class signature: " + signature);
}
}
private static String renameMethodSignature(String signature,
ReplacerClassMap map)
{
try
{
MethodSignature type =
renameType(SignatureAttribute.toMethodSignature(signature), map);
if(type != null)
return type.encode();
return null;
}catch(BadBytecode ex)
{
throw new Error("Can't parse method signature: " + signature);
}
}
private static ClassSignature renameType(ClassSignature type,
ReplacerClassMap map)
{
TypeParameter[] typeParamTypes = type.getParameters();
if(typeParamTypes != null)
{
typeParamTypes =
Arrays.copyOf(typeParamTypes, typeParamTypes.length);
for(int i = 0; i < typeParamTypes.length; i++)
{
TypeParameter newParamType = renameType(typeParamTypes[i], map);
if(newParamType != null)
typeParamTypes[i] = newParamType;
}
}
ClassType superclassType = type.getSuperClass();
if(superclassType != ClassType.OBJECT)
{
ClassType newSuperclassType = renameType(superclassType, map);
if(newSuperclassType != null)
superclassType = newSuperclassType;
}
ClassType[] interfaceTypes = type.getInterfaces();
if(interfaceTypes != null)
{
interfaceTypes =
Arrays.copyOf(interfaceTypes, interfaceTypes.length);
for(int i = 0; i < interfaceTypes.length; i++)
{
ClassType newInterfaceType = renameType(interfaceTypes[i], map);
if(newInterfaceType != null)
interfaceTypes[i] = newInterfaceType;
}
}
return new ClassSignature(typeParamTypes, superclassType,
interfaceTypes);
}
private static MethodSignature renameType(MethodSignature type,
ReplacerClassMap map)
{
TypeParameter[] typeParamTypes = type.getTypeParameters();
if(typeParamTypes != null)
{
typeParamTypes =
Arrays.copyOf(typeParamTypes, typeParamTypes.length);
for(int i = 0; i < typeParamTypes.length; i++)
{
TypeParameter newParamType = renameType(typeParamTypes[i], map);
if(newParamType != null)
typeParamTypes[i] = newParamType;
}
}
Type[] paramTypes = type.getParameterTypes();
if(paramTypes != null)
{
paramTypes = Arrays.copyOf(paramTypes, paramTypes.length);
for(int i = 0; i < paramTypes.length; i++)
{
Type newParamType = renameType(paramTypes[i], map);
if(newParamType != null)
paramTypes[i] = newParamType;
}
}
Type returnType = type.getReturnType();
if(returnType != null)
{
Type newReturnType = renameType(returnType, map);
if(newReturnType != null)
returnType = newReturnType;
}
ObjectType[] exceptionTypes = type.getExceptionTypes();
if(exceptionTypes != null)
{
exceptionTypes =
Arrays.copyOf(exceptionTypes, exceptionTypes.length);
for(int i = 0; i < exceptionTypes.length; i++)
{
ObjectType newExceptionType =
renameType(exceptionTypes[i], map);
if(newExceptionType != null)
exceptionTypes[i] = newExceptionType;
}
}
return new MethodSignature(typeParamTypes, paramTypes, returnType,
exceptionTypes);
}
private static Type renameType(Type type, ReplacerClassMap map)
{
if(type instanceof ObjectType)
return renameType((ObjectType)type, map);
else if(type instanceof BaseType)
return renameType((BaseType)type, map);
else
throw new Error("Don't know how to rename type " + type.getClass());
}
private static ObjectType renameType(ObjectType type, ReplacerClassMap map)
{
if(type instanceof ArrayType)
return renameType((ArrayType)type, map);
else if(type instanceof ClassType)
return renameType((ClassType)type, map);
else if(type instanceof TypeVariable)
return renameType((TypeVariable)type, map);
else
throw new Error("Don't know how to rename type " + type.getClass());
}
private static BaseType renameType(BaseType type, ReplacerClassMap map)
{
// don't have to rename primitives
return null;
}
private static TypeVariable renameType(TypeVariable type,
ReplacerClassMap map)
{
// don't have to rename template args
return null;
}
private static ClassType renameType(ClassType type, ReplacerClassMap map)
{
// translate type args
TypeArgument[] args = type.getTypeArguments();
if(args != null)
{
args = Arrays.copyOf(args, args.length);
for(int i = 0; i < args.length; i++)
{
TypeArgument newType = renameType(args[i], map);
if(newType != null)
args[i] = newType;
}
}
if(type instanceof NestedClassType)
{
NestedClassType nestedType = (NestedClassType)type;
// translate the name
String name = nestedType.getName();
String newName = map.get(getClassName(type));
if(newName != null)
name = new ClassEntry(newName).getInnermostClassName();
// translate the parent class too
ClassType parent = renameType(nestedType.getDeclaringClass(), map);
if(parent == null)
parent = nestedType.getDeclaringClass();
return new NestedClassType(parent, name, args);
}else
{
// translate the name
String name = type.getName();
String newName = renameClassName(name, map);
if(newName != null)
name = newName;
return new ClassType(name, args);
}
}
private static String getClassName(ClassType type)
{
if(type instanceof NestedClassType)
{
NestedClassType nestedType = (NestedClassType)type;
return getClassName(nestedType.getDeclaringClass()) + "$"
+ Descriptor.toJvmName(type.getName());
}else
return Descriptor.toJvmName(type.getName());
}
private static String renameClassName(String name, ReplacerClassMap map)
{
String newName = map.get(Descriptor.toJvmName(name));
if(newName != null)
return Descriptor.toJavaName(newName);
return null;
}
private static TypeArgument renameType(TypeArgument type,
ReplacerClassMap map)
{
ObjectType subType = type.getType();
if(subType != null)
{
ObjectType newSubType = renameType(subType, map);
if(newSubType != null)
switch(type.getKind())
{
case ' ':
return new TypeArgument(newSubType);
case '+':
return TypeArgument.subclassOf(newSubType);
case '-':
return TypeArgument.superOf(newSubType);
default:
throw new Error("Unknown type kind: " + type.getKind());
}
}
return null;
}
private static ArrayType renameType(ArrayType type, ReplacerClassMap map)
{
Type newSubType = renameType(type.getComponentType(), map);
if(newSubType != null)
return new ArrayType(type.getDimension(), newSubType);
return null;
}
private static TypeParameter renameType(TypeParameter type,
ReplacerClassMap map)
{
ObjectType superclassType = type.getClassBound();
if(superclassType != null)
{
ObjectType newSuperclassType = renameType(superclassType, map);
if(newSuperclassType != null)
superclassType = newSuperclassType;
}
ObjectType[] interfaceTypes = type.getInterfaceBound();
if(interfaceTypes != null)
{
interfaceTypes =
Arrays.copyOf(interfaceTypes, interfaceTypes.length);
for(int i = 0; i < interfaceTypes.length; i++)
{
ObjectType newInterfaceType =
renameType(interfaceTypes[i], map);
if(newInterfaceType != null)
interfaceTypes[i] = newInterfaceType;
}
}
return new TypeParameter(type.getName(), superclassType, interfaceTypes);
}
}