summaryrefslogtreecommitdiff
path: root/src/cuchaz/enigma/convert/ClassIdentity.java
diff options
context:
space:
mode:
authorGravatar jeff2015-02-08 21:29:25 -0500
committerGravatar jeff2015-02-08 21:29:25 -0500
commited9b5cdfc648e86fd463bfa8d86b94c41671e14c (patch)
tree2619bbc7e04dfa3b82f8dfd3b1d31f529766cd4b /src/cuchaz/enigma/convert/ClassIdentity.java
downloadenigma-fork-ed9b5cdfc648e86fd463bfa8d86b94c41671e14c.tar.gz
enigma-fork-ed9b5cdfc648e86fd463bfa8d86b94c41671e14c.tar.xz
enigma-fork-ed9b5cdfc648e86fd463bfa8d86b94c41671e14c.zip
switch all classes to new signature/type system
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..1be6123
--- /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.ClassNameReplacer;
54import cuchaz.enigma.mapping.Entry;
55import cuchaz.enigma.mapping.FieldEntry;
56import cuchaz.enigma.mapping.JavassistUtil;
57import cuchaz.enigma.mapping.Signature;
58
59public class ClassIdentity {
60
61 private ClassEntry m_classEntry;
62 private SidedClassNamer m_namer;
63 private Multiset<String> m_fields;
64 private Multiset<String> m_methods;
65 private Multiset<String> m_constructors;
66 private String m_staticInitializer;
67 private String m_extends;
68 private Multiset<String> m_implements;
69 private Multiset<String> m_implementations;
70 private Multiset<String> m_references;
71
72 public ClassIdentity(CtClass c, SidedClassNamer namer, JarIndex index, boolean useReferences) {
73 m_namer = namer;
74
75 // stuff from the bytecode
76
77 m_classEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
78 m_fields = HashMultiset.create();
79 for (CtField field : c.getDeclaredFields()) {
80 m_fields.add(scrubSignature(field.getSignature()));
81 }
82 m_methods = HashMultiset.create();
83 for (CtMethod method : c.getDeclaredMethods()) {
84 m_methods.add(scrubSignature(method.getSignature()) + "0x" + getBehaviorSignature(method));
85 }
86 m_constructors = HashMultiset.create();
87 for (CtConstructor constructor : c.getDeclaredConstructors()) {
88 m_constructors.add(scrubSignature(constructor.getSignature()) + "0x" + getBehaviorSignature(constructor));
89 }
90 m_staticInitializer = "";
91 if (c.getClassInitializer() != null) {
92 m_staticInitializer = getBehaviorSignature(c.getClassInitializer());
93 }
94 m_extends = "";
95 if (c.getClassFile().getSuperclass() != null) {
96 m_extends = scrubClassName(c.getClassFile().getSuperclass());
97 }
98 m_implements = HashMultiset.create();
99 for (String interfaceName : c.getClassFile().getInterfaces()) {
100 m_implements.add(scrubClassName(interfaceName));
101 }
102
103 // stuff from the jar index
104
105 m_implementations = HashMultiset.create();
106 ClassImplementationsTreeNode implementationsNode = index.getClassImplementations(null, m_classEntry);
107 if (implementationsNode != null) {
108 @SuppressWarnings("unchecked")
109 Enumeration<ClassImplementationsTreeNode> implementations = implementationsNode.children();
110 while (implementations.hasMoreElements()) {
111 ClassImplementationsTreeNode node = implementations.nextElement();
112 m_implementations.add(scrubClassName(node.getClassEntry().getName()));
113 }
114 }
115
116 m_references = HashMultiset.create();
117 if (useReferences) {
118 for (CtField field : c.getDeclaredFields()) {
119 FieldEntry fieldEntry = new FieldEntry(m_classEntry, field.getName());
120 for (EntryReference<FieldEntry,BehaviorEntry> reference : index.getFieldReferences(fieldEntry)) {
121 addReference(reference);
122 }
123 }
124 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
125 BehaviorEntry behaviorEntry = JavassistUtil.getBehaviorEntry(behavior);
126 for (EntryReference<BehaviorEntry,BehaviorEntry> reference : index.getBehaviorReferences(behaviorEntry)) {
127 addReference(reference);
128 }
129 }
130 }
131 }
132
133 private void addReference(EntryReference<? extends Entry,BehaviorEntry> reference) {
134 if (reference.context.getSignature() != null) {
135 m_references.add(String.format("%s_%s", scrubClassName(reference.context.getClassName()), scrubSignature(reference.context.getSignature())));
136 } else {
137 m_references.add(String.format("%s_<clinit>", scrubClassName(reference.context.getClassName())));
138 }
139 }
140
141 public ClassEntry getClassEntry() {
142 return m_classEntry;
143 }
144
145 @Override
146 public String toString() {
147 StringBuilder buf = new StringBuilder();
148 buf.append("class: ");
149 buf.append(m_classEntry.getName());
150 buf.append(" ");
151 buf.append(hashCode());
152 buf.append("\n");
153 for (String field : m_fields) {
154 buf.append("\tfield ");
155 buf.append(field);
156 buf.append("\n");
157 }
158 for (String method : m_methods) {
159 buf.append("\tmethod ");
160 buf.append(method);
161 buf.append("\n");
162 }
163 for (String constructor : m_constructors) {
164 buf.append("\tconstructor ");
165 buf.append(constructor);
166 buf.append("\n");
167 }
168 if (m_staticInitializer.length() > 0) {
169 buf.append("\tinitializer ");
170 buf.append(m_staticInitializer);
171 buf.append("\n");
172 }
173 if (m_extends.length() > 0) {
174 buf.append("\textends ");
175 buf.append(m_extends);
176 buf.append("\n");
177 }
178 for (String interfaceName : m_implements) {
179 buf.append("\timplements ");
180 buf.append(interfaceName);
181 buf.append("\n");
182 }
183 for (String implementation : m_implementations) {
184 buf.append("\timplemented by ");
185 buf.append(implementation);
186 buf.append("\n");
187 }
188 for (String reference : m_references) {
189 buf.append("\treference ");
190 buf.append(reference);
191 buf.append("\n");
192 }
193 return buf.toString();
194 }
195
196 private String scrubClassName(String className) {
197 return scrubSignature("L" + Descriptor.toJvmName(className) + ";");
198 }
199
200 private String scrubSignature(String signature) {
201 return scrubSignature(new Signature(signature));
202 }
203
204 private String scrubSignature(Signature signature) {
205
206 return new Signature(signature, new ClassNameReplacer() {
207
208 private Map<String,String> m_classNames = Maps.newHashMap();
209
210 @Override
211 public String replace(String className) {
212
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 }).toString();
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}