From 4be005617b3b8c3578cca07c5d085d12916f0d1d Mon Sep 17 00:00:00 2001
From: lclc98
Date: Thu, 30 Jun 2016 00:49:21 +1000
Subject: Json format (#2)
* Added new format
* Fixed bug
* Updated Version
---
.../java/cuchaz/enigma/convert/ClassIdentity.java | 444 +++++++++++++++++++++
1 file changed, 444 insertions(+)
create mode 100644 src/main/java/cuchaz/enigma/convert/ClassIdentity.java
(limited to 'src/main/java/cuchaz/enigma/convert/ClassIdentity.java')
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 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Jeff Martin.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the GNU Lesser General Public
+ * License v3.0 which accompanies this distribution, and is available at
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma.convert;
+
+import com.google.common.collect.*;
+
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import cuchaz.enigma.Constants;
+import cuchaz.enigma.Util;
+import cuchaz.enigma.analysis.ClassImplementationsTreeNode;
+import cuchaz.enigma.analysis.EntryReference;
+import cuchaz.enigma.analysis.JarIndex;
+import cuchaz.enigma.bytecode.ConstPoolEditor;
+import cuchaz.enigma.bytecode.InfoType;
+import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor;
+import cuchaz.enigma.convert.ClassNamer.SidedClassNamer;
+import cuchaz.enigma.mapping.*;
+import javassist.*;
+import javassist.bytecode.*;
+import javassist.expr.*;
+
+public class ClassIdentity {
+
+ private ClassEntry m_classEntry;
+ private SidedClassNamer m_namer;
+ private Multiset m_fields;
+ private Multiset m_methods;
+ private Multiset m_constructors;
+ private String m_staticInitializer;
+ private String m_extends;
+ private Multiset m_implements;
+ private Set m_stringLiterals;
+ private Multiset m_implementations;
+ private Multiset m_references;
+ private String m_outer;
+
+ private final ClassNameReplacer m_classNameReplacer = new ClassNameReplacer() {
+
+ private Map m_classNames = Maps.newHashMap();
+
+ @Override
+ public String replace(String className) {
+
+ // classes not in the none package can be passed through
+ ClassEntry classEntry = new ClassEntry(className);
+ if (!classEntry.getPackageName().equals(Constants.NonePackage)) {
+ return className;
+ }
+
+ // is this class ourself?
+ if (className.equals(m_classEntry.getName())) {
+ return "CSelf";
+ }
+
+ // try the namer
+ if (m_namer != null) {
+ String newName = m_namer.getName(className);
+ if (newName != null) {
+ return newName;
+ }
+ }
+
+ // otherwise, use local naming
+ if (!m_classNames.containsKey(className)) {
+ m_classNames.put(className, getNewClassName());
+ }
+ return m_classNames.get(className);
+ }
+
+ private String getNewClassName() {
+ return String.format("C%03d", m_classNames.size());
+ }
+ };
+
+ public ClassIdentity(CtClass c, SidedClassNamer namer, JarIndex index, boolean useReferences) {
+ m_namer = namer;
+
+ // stuff from the bytecode
+
+ m_classEntry = EntryFactory.getClassEntry(c);
+ m_fields = HashMultiset.create();
+ for (CtField field : c.getDeclaredFields()) {
+ m_fields.add(scrubType(field.getSignature()));
+ }
+ m_methods = HashMultiset.create();
+ for (CtMethod method : c.getDeclaredMethods()) {
+ m_methods.add(scrubSignature(method.getSignature()) + "0x" + getBehaviorSignature(method));
+ }
+ m_constructors = HashMultiset.create();
+ for (CtConstructor constructor : c.getDeclaredConstructors()) {
+ m_constructors.add(scrubSignature(constructor.getSignature()) + "0x" + getBehaviorSignature(constructor));
+ }
+ m_staticInitializer = "";
+ if (c.getClassInitializer() != null) {
+ m_staticInitializer = getBehaviorSignature(c.getClassInitializer());
+ }
+ m_extends = "";
+ if (c.getClassFile().getSuperclass() != null) {
+ m_extends = scrubClassName(Descriptor.toJvmName(c.getClassFile().getSuperclass()));
+ }
+ m_implements = HashMultiset.create();
+ for (String interfaceName : c.getClassFile().getInterfaces()) {
+ m_implements.add(scrubClassName(Descriptor.toJvmName(interfaceName)));
+ }
+
+ m_stringLiterals = Sets.newHashSet();
+ ConstPool constants = c.getClassFile().getConstPool();
+ for (int i = 1; i < constants.getSize(); i++) {
+ if (constants.getTag(i) == ConstPool.CONST_String) {
+ m_stringLiterals.add(constants.getStringInfo(i));
+ }
+ }
+
+ // stuff from the jar index
+
+ m_implementations = HashMultiset.create();
+ ClassImplementationsTreeNode implementationsNode = index.getClassImplementations(null, m_classEntry);
+ if (implementationsNode != null) {
+ @SuppressWarnings("unchecked")
+ Enumeration implementations = implementationsNode.children();
+ while (implementations.hasMoreElements()) {
+ ClassImplementationsTreeNode node = implementations.nextElement();
+ m_implementations.add(scrubClassName(node.getClassEntry().getName()));
+ }
+ }
+
+ m_references = HashMultiset.create();
+ if (useReferences) {
+ for (CtField field : c.getDeclaredFields()) {
+ FieldEntry fieldEntry = EntryFactory.getFieldEntry(field);
+ index.getFieldReferences(fieldEntry).forEach(this::addReference);
+ }
+ for (CtBehavior behavior : c.getDeclaredBehaviors()) {
+ BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
+ index.getBehaviorReferences(behaviorEntry).forEach(this::addReference);
+ }
+ }
+
+ m_outer = null;
+ if (m_classEntry.isInnerClass()) {
+ m_outer = m_classEntry.getOuterClassName();
+ }
+ }
+
+ private void addReference(EntryReference extends Entry, BehaviorEntry> reference) {
+ if (reference.context.getSignature() != null) {
+ m_references.add(String.format("%s_%s",
+ scrubClassName(reference.context.getClassName()),
+ scrubSignature(reference.context.getSignature())
+ ));
+ } else {
+ m_references.add(String.format("%s_",
+ scrubClassName(reference.context.getClassName())
+ ));
+ }
+ }
+
+ public ClassEntry getClassEntry() {
+ return m_classEntry;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("class: ");
+ buf.append(m_classEntry.getName());
+ buf.append(" ");
+ buf.append(hashCode());
+ buf.append("\n");
+ for (String field : m_fields) {
+ buf.append("\tfield ");
+ buf.append(field);
+ buf.append("\n");
+ }
+ for (String method : m_methods) {
+ buf.append("\tmethod ");
+ buf.append(method);
+ buf.append("\n");
+ }
+ for (String constructor : m_constructors) {
+ buf.append("\tconstructor ");
+ buf.append(constructor);
+ buf.append("\n");
+ }
+ if (m_staticInitializer.length() > 0) {
+ buf.append("\tinitializer ");
+ buf.append(m_staticInitializer);
+ buf.append("\n");
+ }
+ if (m_extends.length() > 0) {
+ buf.append("\textends ");
+ buf.append(m_extends);
+ buf.append("\n");
+ }
+ for (String interfaceName : m_implements) {
+ buf.append("\timplements ");
+ buf.append(interfaceName);
+ buf.append("\n");
+ }
+ for (String implementation : m_implementations) {
+ buf.append("\timplemented by ");
+ buf.append(implementation);
+ buf.append("\n");
+ }
+ for (String reference : m_references) {
+ buf.append("\treference ");
+ buf.append(reference);
+ buf.append("\n");
+ }
+ buf.append("\touter ");
+ buf.append(m_outer);
+ buf.append("\n");
+ return buf.toString();
+ }
+
+ private String scrubClassName(String className) {
+ return m_classNameReplacer.replace(className);
+ }
+
+ private String scrubType(String typeName) {
+ return scrubType(new Type(typeName)).toString();
+ }
+
+ private Type scrubType(Type type) {
+ if (type.hasClass()) {
+ return new Type(type, m_classNameReplacer);
+ } else {
+ return type;
+ }
+ }
+
+ private String scrubSignature(String signature) {
+ return scrubSignature(new Signature(signature)).toString();
+ }
+
+ private Signature scrubSignature(Signature signature) {
+ return new Signature(signature, m_classNameReplacer);
+ }
+
+ private boolean isClassMatchedUniquely(String className) {
+ return m_namer != null && m_namer.getName(Descriptor.toJvmName(className)) != null;
+ }
+
+ private String getBehaviorSignature(CtBehavior behavior) {
+ try {
+ // does this method have an implementation?
+ if (behavior.getMethodInfo().getCodeAttribute() == null) {
+ return "(none)";
+ }
+
+ // compute the hash from the opcodes
+ ConstPool constants = behavior.getMethodInfo().getConstPool();
+ final MessageDigest digest = MessageDigest.getInstance("MD5");
+ CodeIterator iter = behavior.getMethodInfo().getCodeAttribute().iterator();
+ while (iter.hasNext()) {
+ int pos = iter.next();
+
+ // update the hash with the opcode
+ int opcode = iter.byteAt(pos);
+ digest.update((byte) opcode);
+
+ switch (opcode) {
+ case Opcode.LDC: {
+ int constIndex = iter.byteAt(pos + 1);
+ updateHashWithConstant(digest, constants, constIndex);
+ }
+ break;
+
+ case Opcode.LDC_W:
+ case Opcode.LDC2_W: {
+ int constIndex = (iter.byteAt(pos + 1) << 8) | iter.byteAt(pos + 2);
+ updateHashWithConstant(digest, constants, constIndex);
+ }
+ break;
+ }
+ }
+
+ // update hash with method and field accesses
+ behavior.instrument(new ExprEditor() {
+ @Override
+ public void edit(MethodCall call) {
+ updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(call.getClassName())));
+ updateHashWithString(digest, scrubSignature(call.getSignature()));
+ if (isClassMatchedUniquely(call.getClassName())) {
+ updateHashWithString(digest, call.getMethodName());
+ }
+ }
+
+ @Override
+ public void edit(FieldAccess access) {
+ updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(access.getClassName())));
+ updateHashWithString(digest, scrubType(access.getSignature()));
+ if (isClassMatchedUniquely(access.getClassName())) {
+ updateHashWithString(digest, access.getFieldName());
+ }
+ }
+
+ @Override
+ public void edit(ConstructorCall call) {
+ updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(call.getClassName())));
+ updateHashWithString(digest, scrubSignature(call.getSignature()));
+ }
+
+ @Override
+ public void edit(NewExpr expr) {
+ updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(expr.getClassName())));
+ }
+ });
+
+ // convert the hash to a hex string
+ return toHex(digest.digest());
+ } catch (BadBytecode | NoSuchAlgorithmException | CannotCompileException ex) {
+ throw new Error(ex);
+ }
+ }
+
+ private void updateHashWithConstant(MessageDigest digest, ConstPool constants, int index) {
+ ConstPoolEditor editor = new ConstPoolEditor(constants);
+ ConstInfoAccessor item = editor.getItem(index);
+ if (item.getType() == InfoType.StringInfo) {
+ updateHashWithString(digest, constants.getStringInfo(index));
+ }
+ // TODO: other constants
+ }
+
+ private void updateHashWithString(MessageDigest digest, String val) {
+ try {
+ digest.update(val.getBytes("UTF8"));
+ } catch (UnsupportedEncodingException ex) {
+ throw new Error(ex);
+ }
+ }
+
+ private String toHex(byte[] bytes) {
+ // function taken from:
+ // http://stackoverflow.com/questions/9655181/convert-from-byte-array-to-hex-string-in-java
+ final char[] hexArray = "0123456789ABCDEF".toCharArray();
+ char[] hexChars = new char[bytes.length * 2];
+ for (int j = 0; j < bytes.length; j++) {
+ int v = bytes[j] & 0xFF;
+ hexChars[j * 2] = hexArray[v >>> 4];
+ hexChars[j * 2 + 1] = hexArray[v & 0x0F];
+ }
+ return new String(hexChars);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof ClassIdentity) {
+ return equals((ClassIdentity) other);
+ }
+ return false;
+ }
+
+ public boolean equals(ClassIdentity other) {
+ return m_fields.equals(other.m_fields)
+ && m_methods.equals(other.m_methods)
+ && m_constructors.equals(other.m_constructors)
+ && m_staticInitializer.equals(other.m_staticInitializer)
+ && m_extends.equals(other.m_extends)
+ && m_implements.equals(other.m_implements)
+ && m_implementations.equals(other.m_implementations)
+ && m_references.equals(other.m_references);
+ }
+
+ @Override
+ public int hashCode() {
+ List objs = Lists.newArrayList();
+ objs.addAll(m_fields);
+ objs.addAll(m_methods);
+ objs.addAll(m_constructors);
+ objs.add(m_staticInitializer);
+ objs.add(m_extends);
+ objs.addAll(m_implements);
+ objs.addAll(m_implementations);
+ objs.addAll(m_references);
+ return Util.combineHashesOrdered(objs);
+ }
+
+ public int getMatchScore(ClassIdentity other) {
+ return 2 * getNumMatches(m_extends, other.m_extends)
+ + 2 * getNumMatches(m_outer, other.m_outer)
+ + 2 * getNumMatches(m_implements, other.m_implements)
+ + getNumMatches(m_stringLiterals, other.m_stringLiterals)
+ + getNumMatches(m_fields, other.m_fields)
+ + getNumMatches(m_methods, other.m_methods)
+ + getNumMatches(m_constructors, other.m_constructors);
+ }
+
+ public int getMaxMatchScore() {
+ return 2 + 2 + 2 * m_implements.size() + m_stringLiterals.size() + m_fields.size() + m_methods.size() + m_constructors.size();
+ }
+
+ public boolean matches(CtClass c) {
+ // just compare declaration counts
+ return m_fields.size() == c.getDeclaredFields().length
+ && m_methods.size() == c.getDeclaredMethods().length
+ && m_constructors.size() == c.getDeclaredConstructors().length;
+ }
+
+ private int getNumMatches(Set a, Set b) {
+ int numMatches = 0;
+ for (String val : a) {
+ if (b.contains(val)) {
+ numMatches++;
+ }
+ }
+ return numMatches;
+ }
+
+ private int getNumMatches(Multiset a, Multiset b) {
+ int numMatches = 0;
+ for (String val : a) {
+ if (b.contains(val)) {
+ numMatches++;
+ }
+ }
+ return numMatches;
+ }
+
+ private int getNumMatches(String a, String b) {
+ if (a == null && b == null) {
+ return 1;
+ } else if (a != null && b != null && a.equals(b)) {
+ return 1;
+ }
+ return 0;
+ }
+}
--
cgit v1.2.3