From e3f452250e51b7271f3989c7dfd12e4422934942 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 21 May 2015 23:30:00 +0100 Subject: Support Gradle alongside SSJB This makes builds faster, simpler and better automated but still keeps Cuchaz happy. :) --- src/cuchaz/enigma/convert/ClassIdentity.java | 468 +++++++++++++++++++++++++++ 1 file changed, 468 insertions(+) create mode 100644 src/cuchaz/enigma/convert/ClassIdentity.java (limited to 'src/cuchaz/enigma/convert/ClassIdentity.java') diff --git a/src/cuchaz/enigma/convert/ClassIdentity.java b/src/cuchaz/enigma/convert/ClassIdentity.java new file mode 100644 index 0000000..2e164ae --- /dev/null +++ b/src/cuchaz/enigma/convert/ClassIdentity.java @@ -0,0 +1,468 @@ +/******************************************************************************* + * 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 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 javassist.CannotCompileException; +import javassist.CtBehavior; +import javassist.CtClass; +import javassist.CtConstructor; +import javassist.CtField; +import javassist.CtMethod; +import javassist.bytecode.BadBytecode; +import javassist.bytecode.CodeIterator; +import javassist.bytecode.ConstPool; +import javassist.bytecode.Descriptor; +import javassist.bytecode.Opcode; +import javassist.expr.ConstructorCall; +import javassist.expr.ExprEditor; +import javassist.expr.FieldAccess; +import javassist.expr.MethodCall; +import javassist.expr.NewExpr; + +import com.google.common.collect.HashMultiset; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Multiset; +import com.google.common.collect.Sets; + +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.BehaviorEntry; +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.ClassNameReplacer; +import cuchaz.enigma.mapping.Entry; +import cuchaz.enigma.mapping.EntryFactory; +import cuchaz.enigma.mapping.FieldEntry; +import cuchaz.enigma.mapping.Signature; +import cuchaz.enigma.mapping.Type; + +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 = new ClassEntry(Descriptor.toJvmName(c.getName())); + 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 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); + for (EntryReference reference : index.getFieldReferences(fieldEntry)) { + addReference(reference); + } + } + for (CtBehavior behavior : c.getDeclaredBehaviors()) { + BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); + for (EntryReference reference : index.getBehaviorReferences(behaviorEntry)) { + addReference(reference); + } + } + } + + m_outer = EntryFactory.getClassEntry(c).getOuterClassName(); + } + + private void addReference(EntryReference 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.equals(b)) { + return 1; + } + return 0; + } +} -- cgit v1.2.3