summaryrefslogtreecommitdiff
path: root/src/cuchaz/enigma/convert/ClassIdentity.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/cuchaz/enigma/convert/ClassIdentity.java')
-rw-r--r--src/cuchaz/enigma/convert/ClassIdentity.java473
1 files changed, 0 insertions, 473 deletions
diff --git a/src/cuchaz/enigma/convert/ClassIdentity.java b/src/cuchaz/enigma/convert/ClassIdentity.java
deleted file mode 100644
index d9ed08e..0000000
--- a/src/cuchaz/enigma/convert/ClassIdentity.java
+++ /dev/null
@@ -1,473 +0,0 @@
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.convert;
12
13import java.io.UnsupportedEncodingException;
14import java.security.MessageDigest;
15import java.security.NoSuchAlgorithmException;
16import java.util.Enumeration;
17import java.util.List;
18import java.util.Map;
19import java.util.Set;
20
21import javassist.CannotCompileException;
22import javassist.CtBehavior;
23import javassist.CtClass;
24import javassist.CtConstructor;
25import javassist.CtField;
26import javassist.CtMethod;
27import javassist.bytecode.BadBytecode;
28import javassist.bytecode.CodeIterator;
29import javassist.bytecode.ConstPool;
30import javassist.bytecode.Descriptor;
31import javassist.bytecode.Opcode;
32import javassist.expr.ConstructorCall;
33import javassist.expr.ExprEditor;
34import javassist.expr.FieldAccess;
35import javassist.expr.MethodCall;
36import javassist.expr.NewExpr;
37
38import com.google.common.collect.HashMultiset;
39import com.google.common.collect.Lists;
40import com.google.common.collect.Maps;
41import com.google.common.collect.Multiset;
42import com.google.common.collect.Sets;
43
44import cuchaz.enigma.Constants;
45import cuchaz.enigma.Util;
46import cuchaz.enigma.analysis.ClassImplementationsTreeNode;
47import cuchaz.enigma.analysis.EntryReference;
48import cuchaz.enigma.analysis.JarIndex;
49import cuchaz.enigma.bytecode.ConstPoolEditor;
50import cuchaz.enigma.bytecode.InfoType;
51import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor;
52import cuchaz.enigma.convert.ClassNamer.SidedClassNamer;
53import cuchaz.enigma.mapping.BehaviorEntry;
54import cuchaz.enigma.mapping.ClassEntry;
55import cuchaz.enigma.mapping.ClassNameReplacer;
56import cuchaz.enigma.mapping.Entry;
57import cuchaz.enigma.mapping.EntryFactory;
58import cuchaz.enigma.mapping.FieldEntry;
59import cuchaz.enigma.mapping.Signature;
60import cuchaz.enigma.mapping.Type;
61
62public class ClassIdentity {
63
64 private ClassEntry m_classEntry;
65 private SidedClassNamer m_namer;
66 private Multiset<String> m_fields;
67 private Multiset<String> m_methods;
68 private Multiset<String> m_constructors;
69 private String m_staticInitializer;
70 private String m_extends;
71 private Multiset<String> m_implements;
72 private Set<String> m_stringLiterals;
73 private Multiset<String> m_implementations;
74 private Multiset<String> m_references;
75 private String m_outer;
76
77 private final ClassNameReplacer m_classNameReplacer = new ClassNameReplacer() {
78
79 private Map<String,String> m_classNames = Maps.newHashMap();
80
81 @Override
82 public String replace(String className) {
83
84 // classes not in the none package can be passed through
85 ClassEntry classEntry = new ClassEntry(className);
86 if (!classEntry.getPackageName().equals(Constants.NonePackage)) {
87 return className;
88 }
89
90 // is this class ourself?
91 if (className.equals(m_classEntry.getName())) {
92 return "CSelf";
93 }
94
95 // try the namer
96 if (m_namer != null) {
97 String newName = m_namer.getName(className);
98 if (newName != null) {
99 return newName;
100 }
101 }
102
103 // otherwise, use local naming
104 if (!m_classNames.containsKey(className)) {
105 m_classNames.put(className, getNewClassName());
106 }
107 return m_classNames.get(className);
108 }
109
110 private String getNewClassName() {
111 return String.format("C%03d", m_classNames.size());
112 }
113 };
114
115 public ClassIdentity(CtClass c, SidedClassNamer namer, JarIndex index, boolean useReferences) {
116 m_namer = namer;
117
118 // stuff from the bytecode
119
120 m_classEntry = EntryFactory.getClassEntry(c);
121 m_fields = HashMultiset.create();
122 for (CtField field : c.getDeclaredFields()) {
123 m_fields.add(scrubType(field.getSignature()));
124 }
125 m_methods = HashMultiset.create();
126 for (CtMethod method : c.getDeclaredMethods()) {
127 m_methods.add(scrubSignature(method.getSignature()) + "0x" + getBehaviorSignature(method));
128 }
129 m_constructors = HashMultiset.create();
130 for (CtConstructor constructor : c.getDeclaredConstructors()) {
131 m_constructors.add(scrubSignature(constructor.getSignature()) + "0x" + getBehaviorSignature(constructor));
132 }
133 m_staticInitializer = "";
134 if (c.getClassInitializer() != null) {
135 m_staticInitializer = getBehaviorSignature(c.getClassInitializer());
136 }
137 m_extends = "";
138 if (c.getClassFile().getSuperclass() != null) {
139 m_extends = scrubClassName(Descriptor.toJvmName(c.getClassFile().getSuperclass()));
140 }
141 m_implements = HashMultiset.create();
142 for (String interfaceName : c.getClassFile().getInterfaces()) {
143 m_implements.add(scrubClassName(Descriptor.toJvmName(interfaceName)));
144 }
145
146 m_stringLiterals = Sets.newHashSet();
147 ConstPool constants = c.getClassFile().getConstPool();
148 for (int i=1; i<constants.getSize(); i++) {
149 if (constants.getTag(i) == ConstPool.CONST_String) {
150 m_stringLiterals.add(constants.getStringInfo(i));
151 }
152 }
153
154 // stuff from the jar index
155
156 m_implementations = HashMultiset.create();
157 ClassImplementationsTreeNode implementationsNode = index.getClassImplementations(null, m_classEntry);
158 if (implementationsNode != null) {
159 @SuppressWarnings("unchecked")
160 Enumeration<ClassImplementationsTreeNode> implementations = implementationsNode.children();
161 while (implementations.hasMoreElements()) {
162 ClassImplementationsTreeNode node = implementations.nextElement();
163 m_implementations.add(scrubClassName(node.getClassEntry().getName()));
164 }
165 }
166
167 m_references = HashMultiset.create();
168 if (useReferences) {
169 for (CtField field : c.getDeclaredFields()) {
170 FieldEntry fieldEntry = EntryFactory.getFieldEntry(field);
171 for (EntryReference<FieldEntry,BehaviorEntry> reference : index.getFieldReferences(fieldEntry)) {
172 addReference(reference);
173 }
174 }
175 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
176 BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
177 for (EntryReference<BehaviorEntry,BehaviorEntry> reference : index.getBehaviorReferences(behaviorEntry)) {
178 addReference(reference);
179 }
180 }
181 }
182
183 m_outer = null;
184 if (m_classEntry.isInnerClass()) {
185 m_outer = m_classEntry.getOuterClassName();
186 }
187 }
188
189 private void addReference(EntryReference<? extends Entry,BehaviorEntry> reference) {
190 if (reference.context.getSignature() != null) {
191 m_references.add(String.format("%s_%s",
192 scrubClassName(reference.context.getClassName()),
193 scrubSignature(reference.context.getSignature())
194 ));
195 } else {
196 m_references.add(String.format("%s_<clinit>",
197 scrubClassName(reference.context.getClassName())
198 ));
199 }
200 }
201
202 public ClassEntry getClassEntry() {
203 return m_classEntry;
204 }
205
206 @Override
207 public String toString() {
208 StringBuilder buf = new StringBuilder();
209 buf.append("class: ");
210 buf.append(m_classEntry.getName());
211 buf.append(" ");
212 buf.append(hashCode());
213 buf.append("\n");
214 for (String field : m_fields) {
215 buf.append("\tfield ");
216 buf.append(field);
217 buf.append("\n");
218 }
219 for (String method : m_methods) {
220 buf.append("\tmethod ");
221 buf.append(method);
222 buf.append("\n");
223 }
224 for (String constructor : m_constructors) {
225 buf.append("\tconstructor ");
226 buf.append(constructor);
227 buf.append("\n");
228 }
229 if (m_staticInitializer.length() > 0) {
230 buf.append("\tinitializer ");
231 buf.append(m_staticInitializer);
232 buf.append("\n");
233 }
234 if (m_extends.length() > 0) {
235 buf.append("\textends ");
236 buf.append(m_extends);
237 buf.append("\n");
238 }
239 for (String interfaceName : m_implements) {
240 buf.append("\timplements ");
241 buf.append(interfaceName);
242 buf.append("\n");
243 }
244 for (String implementation : m_implementations) {
245 buf.append("\timplemented by ");
246 buf.append(implementation);
247 buf.append("\n");
248 }
249 for (String reference : m_references) {
250 buf.append("\treference ");
251 buf.append(reference);
252 buf.append("\n");
253 }
254 buf.append("\touter ");
255 buf.append(m_outer);
256 buf.append("\n");
257 return buf.toString();
258 }
259
260 private String scrubClassName(String className) {
261 return m_classNameReplacer.replace(className);
262 }
263
264 private String scrubType(String typeName) {
265 return scrubType(new Type(typeName)).toString();
266 }
267
268 private Type scrubType(Type type) {
269 if (type.hasClass()) {
270 return new Type(type, m_classNameReplacer);
271 } else {
272 return type;
273 }
274 }
275
276 private String scrubSignature(String signature) {
277 return scrubSignature(new Signature(signature)).toString();
278 }
279
280 private Signature scrubSignature(Signature signature) {
281 return new Signature(signature, m_classNameReplacer);
282 }
283
284 private boolean isClassMatchedUniquely(String className) {
285 return m_namer != null && m_namer.getName(Descriptor.toJvmName(className)) != null;
286 }
287
288 private String getBehaviorSignature(CtBehavior behavior) {
289 try {
290 // does this method have an implementation?
291 if (behavior.getMethodInfo().getCodeAttribute() == null) {
292 return "(none)";
293 }
294
295 // compute the hash from the opcodes
296 ConstPool constants = behavior.getMethodInfo().getConstPool();
297 final MessageDigest digest = MessageDigest.getInstance("MD5");
298 CodeIterator iter = behavior.getMethodInfo().getCodeAttribute().iterator();
299 while (iter.hasNext()) {
300 int pos = iter.next();
301
302 // update the hash with the opcode
303 int opcode = iter.byteAt(pos);
304 digest.update((byte)opcode);
305
306 switch (opcode) {
307 case Opcode.LDC: {
308 int constIndex = iter.byteAt(pos + 1);
309 updateHashWithConstant(digest, constants, constIndex);
310 }
311 break;
312
313 case Opcode.LDC_W:
314 case Opcode.LDC2_W: {
315 int constIndex = (iter.byteAt(pos + 1) << 8) | iter.byteAt(pos + 2);
316 updateHashWithConstant(digest, constants, constIndex);
317 }
318 break;
319 }
320 }
321
322 // update hash with method and field accesses
323 behavior.instrument(new ExprEditor() {
324 @Override
325 public void edit(MethodCall call) {
326 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(call.getClassName())));
327 updateHashWithString(digest, scrubSignature(call.getSignature()));
328 if (isClassMatchedUniquely(call.getClassName())) {
329 updateHashWithString(digest, call.getMethodName());
330 }
331 }
332
333 @Override
334 public void edit(FieldAccess access) {
335 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(access.getClassName())));
336 updateHashWithString(digest, scrubType(access.getSignature()));
337 if (isClassMatchedUniquely(access.getClassName())) {
338 updateHashWithString(digest, access.getFieldName());
339 }
340 }
341
342 @Override
343 public void edit(ConstructorCall call) {
344 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(call.getClassName())));
345 updateHashWithString(digest, scrubSignature(call.getSignature()));
346 }
347
348 @Override
349 public void edit(NewExpr expr) {
350 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(expr.getClassName())));
351 }
352 });
353
354 // convert the hash to a hex string
355 return toHex(digest.digest());
356 } catch (BadBytecode | NoSuchAlgorithmException | CannotCompileException ex) {
357 throw new Error(ex);
358 }
359 }
360
361 private void updateHashWithConstant(MessageDigest digest, ConstPool constants, int index) {
362 ConstPoolEditor editor = new ConstPoolEditor(constants);
363 ConstInfoAccessor item = editor.getItem(index);
364 if (item.getType() == InfoType.StringInfo) {
365 updateHashWithString(digest, constants.getStringInfo(index));
366 }
367 // TODO: other constants
368 }
369
370 private void updateHashWithString(MessageDigest digest, String val) {
371 try {
372 digest.update(val.getBytes("UTF8"));
373 } catch (UnsupportedEncodingException ex) {
374 throw new Error(ex);
375 }
376 }
377
378 private String toHex(byte[] bytes) {
379 // function taken from:
380 // http://stackoverflow.com/questions/9655181/convert-from-byte-array-to-hex-string-in-java
381 final char[] hexArray = "0123456789ABCDEF".toCharArray();
382 char[] hexChars = new char[bytes.length * 2];
383 for (int j = 0; j < bytes.length; j++) {
384 int v = bytes[j] & 0xFF;
385 hexChars[j * 2] = hexArray[v >>> 4];
386 hexChars[j * 2 + 1] = hexArray[v & 0x0F];
387 }
388 return new String(hexChars);
389 }
390
391 @Override
392 public boolean equals(Object other) {
393 if (other instanceof ClassIdentity) {
394 return equals((ClassIdentity)other);
395 }
396 return false;
397 }
398
399 public boolean equals(ClassIdentity other) {
400 return m_fields.equals(other.m_fields)
401 && m_methods.equals(other.m_methods)
402 && m_constructors.equals(other.m_constructors)
403 && m_staticInitializer.equals(other.m_staticInitializer)
404 && m_extends.equals(other.m_extends)
405 && m_implements.equals(other.m_implements)
406 && m_implementations.equals(other.m_implementations)
407 && m_references.equals(other.m_references);
408 }
409
410 @Override
411 public int hashCode() {
412 List<Object> objs = Lists.newArrayList();
413 objs.addAll(m_fields);
414 objs.addAll(m_methods);
415 objs.addAll(m_constructors);
416 objs.add(m_staticInitializer);
417 objs.add(m_extends);
418 objs.addAll(m_implements);
419 objs.addAll(m_implementations);
420 objs.addAll(m_references);
421 return Util.combineHashesOrdered(objs);
422 }
423
424 public int getMatchScore(ClassIdentity other) {
425 return 2*getNumMatches(m_extends, other.m_extends)
426 + 2*getNumMatches(m_outer, other.m_outer)
427 + 2*getNumMatches(m_implements, other.m_implements)
428 + getNumMatches(m_stringLiterals, other.m_stringLiterals)
429 + getNumMatches(m_fields, other.m_fields)
430 + getNumMatches(m_methods, other.m_methods)
431 + getNumMatches(m_constructors, other.m_constructors);
432 }
433
434 public int getMaxMatchScore() {
435 return 2 + 2 + 2*m_implements.size() + m_stringLiterals.size() + m_fields.size() + m_methods.size() + m_constructors.size();
436 }
437
438 public boolean matches(CtClass c) {
439 // just compare declaration counts
440 return m_fields.size() == c.getDeclaredFields().length
441 && m_methods.size() == c.getDeclaredMethods().length
442 && m_constructors.size() == c.getDeclaredConstructors().length;
443 }
444
445 private int getNumMatches(Set<String> a, Set<String> b) {
446 int numMatches = 0;
447 for (String val : a) {
448 if (b.contains(val)) {
449 numMatches++;
450 }
451 }
452 return numMatches;
453 }
454
455 private int getNumMatches(Multiset<String> a, Multiset<String> b) {
456 int numMatches = 0;
457 for (String val : a) {
458 if (b.contains(val)) {
459 numMatches++;
460 }
461 }
462 return numMatches;
463 }
464
465 private int getNumMatches(String a, String b) {
466 if (a == null && b == null) {
467 return 1;
468 } else if (a != null && b != null && a.equals(b)) {
469 return 1;
470 }
471 return 0;
472 }
473}