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