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