summaryrefslogtreecommitdiff
path: root/src/cuchaz/enigma/bytecode/ClassRenamer.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/cuchaz/enigma/bytecode/ClassRenamer.java')
-rw-r--r--src/cuchaz/enigma/bytecode/ClassRenamer.java544
1 files changed, 544 insertions, 0 deletions
diff --git a/src/cuchaz/enigma/bytecode/ClassRenamer.java b/src/cuchaz/enigma/bytecode/ClassRenamer.java
new file mode 100644
index 0000000..4d95f30
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/ClassRenamer.java
@@ -0,0 +1,544 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import java.lang.reflect.InvocationTargetException;
14import java.lang.reflect.Method;
15import java.util.Arrays;
16import java.util.HashMap;
17import java.util.List;
18import java.util.Map;
19
20import javassist.CtClass;
21import javassist.bytecode.AttributeInfo;
22import javassist.bytecode.BadBytecode;
23import javassist.bytecode.ByteArray;
24import javassist.bytecode.ClassFile;
25import javassist.bytecode.CodeAttribute;
26import javassist.bytecode.ConstPool;
27import javassist.bytecode.Descriptor;
28import javassist.bytecode.FieldInfo;
29import javassist.bytecode.InnerClassesAttribute;
30import javassist.bytecode.LocalVariableTypeAttribute;
31import javassist.bytecode.MethodInfo;
32import javassist.bytecode.SignatureAttribute;
33import javassist.bytecode.SignatureAttribute.ArrayType;
34import javassist.bytecode.SignatureAttribute.BaseType;
35import javassist.bytecode.SignatureAttribute.ClassSignature;
36import javassist.bytecode.SignatureAttribute.ClassType;
37import javassist.bytecode.SignatureAttribute.MethodSignature;
38import javassist.bytecode.SignatureAttribute.NestedClassType;
39import javassist.bytecode.SignatureAttribute.ObjectType;
40import javassist.bytecode.SignatureAttribute.Type;
41import javassist.bytecode.SignatureAttribute.TypeArgument;
42import javassist.bytecode.SignatureAttribute.TypeParameter;
43import javassist.bytecode.SignatureAttribute.TypeVariable;
44import cuchaz.enigma.mapping.ClassEntry;
45import cuchaz.enigma.mapping.ClassNameReplacer;
46import cuchaz.enigma.mapping.Translator;
47
48public class ClassRenamer {
49
50 private static enum SignatureType {
51 Class {
52
53 @Override
54 public String rename(String signature, ReplacerClassMap map) {
55 return renameClassSignature(signature, map);
56 }
57 },
58 Field {
59
60 @Override
61 public String rename(String signature, ReplacerClassMap map) {
62 return renameFieldSignature(signature, map);
63 }
64 },
65 Method {
66
67 @Override
68 public String rename(String signature, ReplacerClassMap map) {
69 return renameMethodSignature(signature, map);
70 }
71 };
72
73 public abstract String rename(String signature, ReplacerClassMap map);
74 }
75
76 private static class ReplacerClassMap extends HashMap<String,String> {
77
78 private static final long serialVersionUID = 317915213205066168L;
79
80 private ClassNameReplacer m_replacer;
81
82 public ReplacerClassMap(ClassNameReplacer replacer) {
83 m_replacer = replacer;
84 }
85
86 @Override
87 public String get(Object obj) {
88 if (obj instanceof String) {
89 return get((String)obj);
90 }
91 return null;
92 }
93
94 public String get(String className) {
95 return m_replacer.replace(className);
96 }
97 }
98
99 public static void renameClasses(CtClass c, final Translator translator) {
100 renameClasses(c, new ClassNameReplacer() {
101 @Override
102 public String replace(String className) {
103 ClassEntry entry = translator.translateEntry(new ClassEntry(className));
104 if (entry != null) {
105 return entry.getName();
106 }
107 return null;
108 }
109 });
110 }
111
112 public static void moveAllClassesOutOfDefaultPackage(CtClass c, final String newPackageName) {
113 renameClasses(c, new ClassNameReplacer() {
114 @Override
115 public String replace(String className) {
116 ClassEntry entry = new ClassEntry(className);
117 if (entry.isInDefaultPackage()) {
118 return newPackageName + "/" + entry.getName();
119 }
120 return null;
121 }
122 });
123 }
124
125 public static void moveAllClassesIntoDefaultPackage(CtClass c, final String oldPackageName) {
126 renameClasses(c, new ClassNameReplacer() {
127 @Override
128 public String replace(String className) {
129 ClassEntry entry = new ClassEntry(className);
130 if (entry.getPackageName().equals(oldPackageName)) {
131 return entry.getSimpleName();
132 }
133 return null;
134 }
135 });
136 }
137
138 @SuppressWarnings("unchecked")
139 public static void renameClasses(CtClass c, ClassNameReplacer replacer) {
140
141 // sadly, we can't use CtClass.renameClass() because SignatureAttribute.renameClass() is extremely buggy =(
142
143 ReplacerClassMap map = new ReplacerClassMap(replacer);
144 ClassFile classFile = c.getClassFile();
145
146 // rename the constant pool (covers ClassInfo, MethodTypeInfo, and NameAndTypeInfo)
147 ConstPool constPool = c.getClassFile().getConstPool();
148 constPool.renameClass(map);
149
150 // rename class attributes
151 renameAttributes(classFile.getAttributes(), map, SignatureType.Class);
152
153 // rename methods
154 for (MethodInfo methodInfo : (List<MethodInfo>)classFile.getMethods()) {
155 methodInfo.setDescriptor(Descriptor.rename(methodInfo.getDescriptor(), map));
156 renameAttributes(methodInfo.getAttributes(), map, SignatureType.Method);
157 }
158
159 // rename fields
160 for (FieldInfo fieldInfo : (List<FieldInfo>)classFile.getFields()) {
161 fieldInfo.setDescriptor(Descriptor.rename(fieldInfo.getDescriptor(), map));
162 renameAttributes(fieldInfo.getAttributes(), map, SignatureType.Field);
163 }
164
165 // rename the class name itself last
166 // NOTE: don't use the map here, because setName() calls the buggy SignatureAttribute.renameClass()
167 // we only want to replace exactly this class name
168 String newName = renameClassName(c.getName(), map);
169 if (newName != null) {
170 c.setName(newName);
171 }
172
173 // replace simple names in the InnerClasses attribute too
174 InnerClassesAttribute attr = (InnerClassesAttribute)c.getClassFile().getAttribute(InnerClassesAttribute.tag);
175 if (attr != null) {
176 for (int i = 0; i < attr.tableLength(); i++) {
177
178 // get the inner class full name (which has already been translated)
179 ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(attr.innerClass(i)));
180
181 if (attr.innerNameIndex(i) != 0) {
182 // update the inner name
183 attr.setInnerNameIndex(i, constPool.addUtf8Info(classEntry.getInnermostClassName()));
184 }
185
186 /* DEBUG
187 System.out.println(String.format("\tDEOBF: %s-> ATTR: %s,%s,%s", classEntry, attr.outerClass(i), attr.innerClass(i), attr.innerName(i)));
188 */
189 }
190 }
191 }
192
193 @SuppressWarnings("unchecked")
194 private static void renameAttributes(List<AttributeInfo> attributes, ReplacerClassMap map, SignatureType type) {
195 try {
196
197 // make the rename class method accessible
198 Method renameClassMethod = AttributeInfo.class.getDeclaredMethod("renameClass", Map.class);
199 renameClassMethod.setAccessible(true);
200
201 for (AttributeInfo attribute : attributes) {
202 if (attribute instanceof SignatureAttribute) {
203 // this has to be handled specially because SignatureAttribute.renameClass() is buggy as hell
204 SignatureAttribute signatureAttribute = (SignatureAttribute)attribute;
205 String newSignature = type.rename(signatureAttribute.getSignature(), map);
206 if (newSignature != null) {
207 signatureAttribute.setSignature(newSignature);
208 }
209 } else if (attribute instanceof CodeAttribute) {
210 // code attributes have signature attributes too (indirectly)
211 CodeAttribute codeAttribute = (CodeAttribute)attribute;
212 renameAttributes(codeAttribute.getAttributes(), map, type);
213 } else if (attribute instanceof LocalVariableTypeAttribute) {
214 // lvt attributes have signature attributes too
215 LocalVariableTypeAttribute localVariableAttribute = (LocalVariableTypeAttribute)attribute;
216 renameLocalVariableTypeAttribute(localVariableAttribute, map);
217 } else {
218 renameClassMethod.invoke(attribute, map);
219 }
220 }
221
222 } catch(NoSuchMethodException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
223 throw new Error("Unable to call javassist methods by reflection!", ex);
224 }
225 }
226
227 private static void renameLocalVariableTypeAttribute(LocalVariableTypeAttribute attribute, ReplacerClassMap map) {
228
229 // adapted from LocalVariableAttribute.renameClass()
230 ConstPool cp = attribute.getConstPool();
231 int n = attribute.tableLength();
232 byte[] info = attribute.get();
233 for (int i = 0; i < n; ++i) {
234 int pos = i * 10 + 2;
235 int index = ByteArray.readU16bit(info, pos + 6);
236 if (index != 0) {
237 String signature = cp.getUtf8Info(index);
238 String newSignature = renameLocalVariableSignature(signature, map);
239 if (newSignature != null) {
240 ByteArray.write16bit(cp.addUtf8Info(newSignature), info, pos + 6);
241 }
242 }
243 }
244 }
245
246 private static String renameLocalVariableSignature(String signature, ReplacerClassMap map) {
247
248 // for some reason, signatures with . in them don't count as field signatures
249 // looks like anonymous classes delimit with . in stead of $
250 // convert the . to $, but keep track of how many we replace
251 // we need to put them back after we translate
252 int start = signature.lastIndexOf('$') + 1;
253 int numConverted = 0;
254 StringBuilder buf = new StringBuilder(signature);
255 for (int i=buf.length()-1; i>=start; i--) {
256 char c = buf.charAt(i);
257 if (c == '.') {
258 buf.setCharAt(i, '$');
259 numConverted++;
260 }
261 }
262 signature = buf.toString();
263
264 // translate
265 String newSignature = renameFieldSignature(signature, map);
266 if (newSignature != null) {
267
268 // put the delimiters back
269 buf = new StringBuilder(newSignature);
270 for (int i=buf.length()-1; i>=0 && numConverted > 0; i--) {
271 char c = buf.charAt(i);
272 if (c == '$') {
273 buf.setCharAt(i, '.');
274 numConverted--;
275 }
276 }
277 assert(numConverted == 0);
278 newSignature = buf.toString();
279
280 return newSignature;
281 }
282
283 return null;
284 }
285
286 private static String renameClassSignature(String signature, ReplacerClassMap map) {
287 try {
288 ClassSignature type = renameType(SignatureAttribute.toClassSignature(signature), map);
289 if (type != null) {
290 return type.encode();
291 }
292 return null;
293 } catch (BadBytecode ex) {
294 throw new Error("Can't parse field signature: " + signature);
295 }
296 }
297
298 private static String renameFieldSignature(String signature, ReplacerClassMap map) {
299 try {
300 ObjectType type = renameType(SignatureAttribute.toFieldSignature(signature), map);
301 if (type != null) {
302 return type.encode();
303 }
304 return null;
305 } catch (BadBytecode ex) {
306 throw new Error("Can't parse class signature: " + signature);
307 }
308 }
309
310 private static String renameMethodSignature(String signature, ReplacerClassMap map) {
311 try {
312 MethodSignature type = renameType(SignatureAttribute.toMethodSignature(signature), map);
313 if (type != null) {
314 return type.encode();
315 }
316 return null;
317 } catch (BadBytecode ex) {
318 throw new Error("Can't parse method signature: " + signature);
319 }
320 }
321
322 private static ClassSignature renameType(ClassSignature type, ReplacerClassMap map) {
323
324 TypeParameter[] typeParamTypes = type.getParameters();
325 if (typeParamTypes != null) {
326 typeParamTypes = Arrays.copyOf(typeParamTypes, typeParamTypes.length);
327 for (int i=0; i<typeParamTypes.length; i++) {
328 TypeParameter newParamType = renameType(typeParamTypes[i], map);
329 if (newParamType != null) {
330 typeParamTypes[i] = newParamType;
331 }
332 }
333 }
334
335 ClassType superclassType = type.getSuperClass();
336 if (superclassType != ClassType.OBJECT) {
337 ClassType newSuperclassType = renameType(superclassType, map);
338 if (newSuperclassType != null) {
339 superclassType = newSuperclassType;
340 }
341 }
342
343 ClassType[] interfaceTypes = type.getInterfaces();
344 if (interfaceTypes != null) {
345 interfaceTypes = Arrays.copyOf(interfaceTypes, interfaceTypes.length);
346 for (int i=0; i<interfaceTypes.length; i++) {
347 ClassType newInterfaceType = renameType(interfaceTypes[i], map);
348 if (newInterfaceType != null) {
349 interfaceTypes[i] = newInterfaceType;
350 }
351 }
352 }
353
354 return new ClassSignature(typeParamTypes, superclassType, interfaceTypes);
355 }
356
357 private static MethodSignature renameType(MethodSignature type, ReplacerClassMap map) {
358
359 TypeParameter[] typeParamTypes = type.getTypeParameters();
360 if (typeParamTypes != null) {
361 typeParamTypes = Arrays.copyOf(typeParamTypes, typeParamTypes.length);
362 for (int i=0; i<typeParamTypes.length; i++) {
363 TypeParameter newParamType = renameType(typeParamTypes[i], map);
364 if (newParamType != null) {
365 typeParamTypes[i] = newParamType;
366 }
367 }
368 }
369
370 Type[] paramTypes = type.getParameterTypes();
371 if (paramTypes != null) {
372 paramTypes = Arrays.copyOf(paramTypes, paramTypes.length);
373 for (int i=0; i<paramTypes.length; i++) {
374 Type newParamType = renameType(paramTypes[i], map);
375 if (newParamType != null) {
376 paramTypes[i] = newParamType;
377 }
378 }
379 }
380
381 Type returnType = type.getReturnType();
382 if (returnType != null) {
383 Type newReturnType = renameType(returnType, map);
384 if (newReturnType != null) {
385 returnType = newReturnType;
386 }
387 }
388
389 ObjectType[] exceptionTypes = type.getExceptionTypes();
390 if (exceptionTypes != null) {
391 exceptionTypes = Arrays.copyOf(exceptionTypes, exceptionTypes.length);
392 for (int i=0; i<exceptionTypes.length; i++) {
393 ObjectType newExceptionType = renameType(exceptionTypes[i], map);
394 if (newExceptionType != null) {
395 exceptionTypes[i] = newExceptionType;
396 }
397 }
398 }
399
400 return new MethodSignature(typeParamTypes, paramTypes, returnType, exceptionTypes);
401 }
402
403 private static Type renameType(Type type, ReplacerClassMap map) {
404 if (type instanceof ObjectType) {
405 return renameType((ObjectType)type, map);
406 } else if (type instanceof BaseType) {
407 return renameType((BaseType)type, map);
408 } else {
409 throw new Error("Don't know how to rename type " + type.getClass());
410 }
411 }
412
413 private static ObjectType renameType(ObjectType type, ReplacerClassMap map) {
414 if (type instanceof ArrayType) {
415 return renameType((ArrayType)type, map);
416 } else if (type instanceof ClassType) {
417 return renameType((ClassType)type, map);
418 } else if (type instanceof TypeVariable) {
419 return renameType((TypeVariable)type, map);
420 } else {
421 throw new Error("Don't know how to rename type " + type.getClass());
422 }
423 }
424
425 private static BaseType renameType(BaseType type, ReplacerClassMap map) {
426 // don't have to rename primitives
427 return null;
428 }
429
430 private static TypeVariable renameType(TypeVariable type, ReplacerClassMap map) {
431 // don't have to rename template args
432 return null;
433 }
434
435 private static ClassType renameType(ClassType type, ReplacerClassMap map) {
436
437 // translate type args
438 TypeArgument[] args = type.getTypeArguments();
439 if (args != null) {
440 args = Arrays.copyOf(args, args.length);
441 for (int i=0; i<args.length; i++) {
442 TypeArgument newType = renameType(args[i], map);
443 if (newType != null) {
444 args[i] = newType;
445 }
446 }
447 }
448
449 if (type instanceof NestedClassType) {
450 NestedClassType nestedType = (NestedClassType)type;
451
452 // translate the name
453 String name = nestedType.getName();
454 String newName = map.get(getClassName(type));
455 if (newName != null) {
456 name = new ClassEntry(newName).getInnermostClassName();
457 }
458
459 // translate the parent class too
460 ClassType parent = renameType(nestedType.getDeclaringClass(), map);
461 if (parent == null) {
462 parent = nestedType.getDeclaringClass();
463 }
464
465 return new NestedClassType(parent, name, args);
466 } else {
467
468 // translate the name
469 String name = type.getName();
470 String newName = renameClassName(name, map);
471 if (newName != null) {
472 name = newName;
473 }
474
475 return new ClassType(name, args);
476 }
477 }
478
479 private static String getClassName(ClassType type) {
480 if (type instanceof NestedClassType) {
481 NestedClassType nestedType = (NestedClassType)type;
482 return getClassName(nestedType.getDeclaringClass()) + "$" + Descriptor.toJvmName(type.getName());
483 } else {
484 return Descriptor.toJvmName(type.getName());
485 }
486 }
487
488 private static String renameClassName(String name, ReplacerClassMap map) {
489 String newName = map.get(Descriptor.toJvmName(name));
490 if (newName != null) {
491 return Descriptor.toJavaName(newName);
492 }
493 return null;
494 }
495
496 private static TypeArgument renameType(TypeArgument type, ReplacerClassMap map) {
497 ObjectType subType = type.getType();
498 if (subType != null) {
499 ObjectType newSubType = renameType(subType, map);
500 if (newSubType != null) {
501 switch (type.getKind()) {
502 case ' ': return new TypeArgument(newSubType);
503 case '+': return TypeArgument.subclassOf(newSubType);
504 case '-': return TypeArgument.superOf(newSubType);
505 default:
506 throw new Error("Unknown type kind: " + type.getKind());
507 }
508 }
509 }
510 return null;
511 }
512
513 private static ArrayType renameType(ArrayType type, ReplacerClassMap map) {
514 Type newSubType = renameType(type.getComponentType(), map);
515 if (newSubType != null) {
516 return new ArrayType(type.getDimension(), newSubType);
517 }
518 return null;
519 }
520
521 private static TypeParameter renameType(TypeParameter type, ReplacerClassMap map) {
522
523 ObjectType superclassType = type.getClassBound();
524 if (superclassType != null) {
525 ObjectType newSuperclassType = renameType(superclassType, map);
526 if (newSuperclassType != null) {
527 superclassType = newSuperclassType;
528 }
529 }
530
531 ObjectType[] interfaceTypes = type.getInterfaceBound();
532 if (interfaceTypes != null) {
533 interfaceTypes = Arrays.copyOf(interfaceTypes, interfaceTypes.length);
534 for (int i=0; i<interfaceTypes.length; i++) {
535 ObjectType newInterfaceType = renameType(interfaceTypes[i], map);
536 if (newInterfaceType != null) {
537 interfaceTypes[i] = newInterfaceType;
538 }
539 }
540 }
541
542 return new TypeParameter(type.getName(), superclassType, interfaceTypes);
543 }
544}