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