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/bytecode/CheckCastIterator.java | 127 +++++ src/cuchaz/enigma/bytecode/ClassProtectifier.java | 51 ++ src/cuchaz/enigma/bytecode/ClassPublifier.java | 51 ++ src/cuchaz/enigma/bytecode/ClassRenamer.java | 544 +++++++++++++++++++++ src/cuchaz/enigma/bytecode/ClassTranslator.java | 157 ++++++ src/cuchaz/enigma/bytecode/ConstPoolEditor.java | 263 ++++++++++ src/cuchaz/enigma/bytecode/InfoType.java | 317 ++++++++++++ src/cuchaz/enigma/bytecode/InnerClassWriter.java | 132 +++++ .../enigma/bytecode/LocalVariableRenamer.java | 123 +++++ .../enigma/bytecode/MethodParameterWriter.java | 70 +++ .../enigma/bytecode/MethodParametersAttribute.java | 86 ++++ .../bytecode/accessors/ClassInfoAccessor.java | 55 +++ .../bytecode/accessors/ConstInfoAccessor.java | 156 ++++++ .../accessors/InvokeDynamicInfoAccessor.java | 74 +++ .../bytecode/accessors/MemberRefInfoAccessor.java | 74 +++ .../accessors/MethodHandleInfoAccessor.java | 74 +++ .../bytecode/accessors/MethodTypeInfoAccessor.java | 55 +++ .../accessors/NameAndTypeInfoAccessor.java | 74 +++ .../bytecode/accessors/StringInfoAccessor.java | 55 +++ .../bytecode/accessors/Utf8InfoAccessor.java | 28 ++ 20 files changed, 2566 insertions(+) create mode 100644 src/cuchaz/enigma/bytecode/CheckCastIterator.java create mode 100644 src/cuchaz/enigma/bytecode/ClassProtectifier.java create mode 100644 src/cuchaz/enigma/bytecode/ClassPublifier.java create mode 100644 src/cuchaz/enigma/bytecode/ClassRenamer.java create mode 100644 src/cuchaz/enigma/bytecode/ClassTranslator.java create mode 100644 src/cuchaz/enigma/bytecode/ConstPoolEditor.java create mode 100644 src/cuchaz/enigma/bytecode/InfoType.java create mode 100644 src/cuchaz/enigma/bytecode/InnerClassWriter.java create mode 100644 src/cuchaz/enigma/bytecode/LocalVariableRenamer.java create mode 100644 src/cuchaz/enigma/bytecode/MethodParameterWriter.java create mode 100644 src/cuchaz/enigma/bytecode/MethodParametersAttribute.java create mode 100644 src/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java create mode 100644 src/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java create mode 100644 src/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java create mode 100644 src/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java create mode 100644 src/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java create mode 100644 src/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java create mode 100644 src/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java create mode 100644 src/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java create mode 100644 src/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java (limited to 'src/cuchaz/enigma/bytecode') diff --git a/src/cuchaz/enigma/bytecode/CheckCastIterator.java b/src/cuchaz/enigma/bytecode/CheckCastIterator.java new file mode 100644 index 0000000..517b9d6 --- /dev/null +++ b/src/cuchaz/enigma/bytecode/CheckCastIterator.java @@ -0,0 +1,127 @@ +/******************************************************************************* + * 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.bytecode; + +import java.util.Iterator; + +import javassist.bytecode.BadBytecode; +import javassist.bytecode.CodeAttribute; +import javassist.bytecode.CodeIterator; +import javassist.bytecode.ConstPool; +import javassist.bytecode.Descriptor; +import javassist.bytecode.Opcode; +import cuchaz.enigma.bytecode.CheckCastIterator.CheckCast; +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.MethodEntry; +import cuchaz.enigma.mapping.Signature; + +public class CheckCastIterator implements Iterator { + + public static class CheckCast { + + public String className; + public MethodEntry prevMethodEntry; + + public CheckCast(String className, MethodEntry prevMethodEntry) { + this.className = className; + this.prevMethodEntry = prevMethodEntry; + } + } + + private ConstPool m_constants; + private CodeAttribute m_attribute; + private CodeIterator m_iter; + private CheckCast m_next; + + public CheckCastIterator(CodeAttribute codeAttribute) throws BadBytecode { + m_constants = codeAttribute.getConstPool(); + m_attribute = codeAttribute; + m_iter = m_attribute.iterator(); + + m_next = getNext(); + } + + @Override + public boolean hasNext() { + return m_next != null; + } + + @Override + public CheckCast next() { + CheckCast out = m_next; + try { + m_next = getNext(); + } catch (BadBytecode ex) { + throw new Error(ex); + } + return out; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + private CheckCast getNext() throws BadBytecode { + int prevPos = 0; + while (m_iter.hasNext()) { + int pos = m_iter.next(); + int opcode = m_iter.byteAt(pos); + switch (opcode) { + case Opcode.CHECKCAST: + + // get the type of this op code (next two bytes are a classinfo index) + MethodEntry prevMethodEntry = getMethodEntry(prevPos); + if (prevMethodEntry != null) { + return new CheckCast(m_constants.getClassInfo(m_iter.s16bitAt(pos + 1)), prevMethodEntry); + } + break; + } + prevPos = pos; + } + return null; + } + + private MethodEntry getMethodEntry(int pos) { + switch (m_iter.byteAt(pos)) { + case Opcode.INVOKEVIRTUAL: + case Opcode.INVOKESTATIC: + case Opcode.INVOKEDYNAMIC: + case Opcode.INVOKESPECIAL: { + int index = m_iter.s16bitAt(pos + 1); + return new MethodEntry( + new ClassEntry(Descriptor.toJvmName(m_constants.getMethodrefClassName(index))), + m_constants.getMethodrefName(index), + new Signature(m_constants.getMethodrefType(index)) + ); + } + + case Opcode.INVOKEINTERFACE: { + int index = m_iter.s16bitAt(pos + 1); + return new MethodEntry( + new ClassEntry(Descriptor.toJvmName(m_constants.getInterfaceMethodrefClassName(index))), + m_constants.getInterfaceMethodrefName(index), + new Signature(m_constants.getInterfaceMethodrefType(index)) + ); + } + } + return null; + } + + public Iterable casts() { + return new Iterable() { + @Override + public Iterator iterator() { + return CheckCastIterator.this; + } + }; + } +} diff --git a/src/cuchaz/enigma/bytecode/ClassProtectifier.java b/src/cuchaz/enigma/bytecode/ClassProtectifier.java new file mode 100644 index 0000000..f1ee4e7 --- /dev/null +++ b/src/cuchaz/enigma/bytecode/ClassProtectifier.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * 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.bytecode; + +import javassist.CtBehavior; +import javassist.CtClass; +import javassist.CtField; +import javassist.bytecode.AccessFlag; +import javassist.bytecode.InnerClassesAttribute; + + +public class ClassProtectifier { + + public static CtClass protectify(CtClass c) { + + // protectify all the fields + for (CtField field : c.getDeclaredFields()) { + field.setModifiers(protectify(field.getModifiers())); + } + + // protectify all the methods and constructors + for (CtBehavior behavior : c.getDeclaredBehaviors()) { + behavior.setModifiers(protectify(behavior.getModifiers())); + } + + // protectify all the inner classes + InnerClassesAttribute attr = (InnerClassesAttribute)c.getClassFile().getAttribute(InnerClassesAttribute.tag); + if (attr != null) { + for (int i=0; i { + + private static final long serialVersionUID = 317915213205066168L; + + private ClassNameReplacer m_replacer; + + public ReplacerClassMap(ClassNameReplacer replacer) { + m_replacer = replacer; + } + + @Override + public String get(Object obj) { + if (obj instanceof String) { + return get((String)obj); + } + return null; + } + + public String get(String className) { + return m_replacer.replace(className); + } + } + + public static void renameClasses(CtClass c, final Translator translator) { + renameClasses(c, new ClassNameReplacer() { + @Override + public String replace(String className) { + ClassEntry entry = translator.translateEntry(new ClassEntry(className)); + if (entry != null) { + return entry.getName(); + } + return null; + } + }); + } + + public static void moveAllClassesOutOfDefaultPackage(CtClass c, final String newPackageName) { + renameClasses(c, new ClassNameReplacer() { + @Override + public String replace(String className) { + ClassEntry entry = new ClassEntry(className); + if (entry.isInDefaultPackage()) { + return newPackageName + "/" + entry.getName(); + } + return null; + } + }); + } + + public static void moveAllClassesIntoDefaultPackage(CtClass c, final String oldPackageName) { + renameClasses(c, new ClassNameReplacer() { + @Override + public String replace(String className) { + ClassEntry entry = new ClassEntry(className); + if (entry.getPackageName().equals(oldPackageName)) { + return entry.getSimpleName(); + } + return null; + } + }); + } + + @SuppressWarnings("unchecked") + public static void renameClasses(CtClass c, ClassNameReplacer replacer) { + + // sadly, we can't use CtClass.renameClass() because SignatureAttribute.renameClass() is extremely buggy =( + + ReplacerClassMap map = new ReplacerClassMap(replacer); + ClassFile classFile = c.getClassFile(); + + // rename the constant pool (covers ClassInfo, MethodTypeInfo, and NameAndTypeInfo) + ConstPool constPool = c.getClassFile().getConstPool(); + constPool.renameClass(map); + + // rename class attributes + renameAttributes(classFile.getAttributes(), map, SignatureType.Class); + + // rename methods + for (MethodInfo methodInfo : (List)classFile.getMethods()) { + methodInfo.setDescriptor(Descriptor.rename(methodInfo.getDescriptor(), map)); + renameAttributes(methodInfo.getAttributes(), map, SignatureType.Method); + } + + // rename fields + for (FieldInfo fieldInfo : (List)classFile.getFields()) { + fieldInfo.setDescriptor(Descriptor.rename(fieldInfo.getDescriptor(), map)); + renameAttributes(fieldInfo.getAttributes(), map, SignatureType.Field); + } + + // rename the class name itself last + // NOTE: don't use the map here, because setName() calls the buggy SignatureAttribute.renameClass() + // we only want to replace exactly this class name + String newName = renameClassName(c.getName(), map); + if (newName != null) { + c.setName(newName); + } + + // replace simple names in the InnerClasses attribute too + InnerClassesAttribute attr = (InnerClassesAttribute)c.getClassFile().getAttribute(InnerClassesAttribute.tag); + if (attr != null) { + for (int i = 0; i < attr.tableLength(); i++) { + + // get the inner class full name (which has already been translated) + ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(attr.innerClass(i))); + + if (attr.innerNameIndex(i) != 0) { + // update the inner name + attr.setInnerNameIndex(i, constPool.addUtf8Info(classEntry.getInnermostClassName())); + } + + /* DEBUG + System.out.println(String.format("\tDEOBF: %s-> ATTR: %s,%s,%s", classEntry, attr.outerClass(i), attr.innerClass(i), attr.innerName(i))); + */ + } + } + } + + @SuppressWarnings("unchecked") + private static void renameAttributes(List attributes, ReplacerClassMap map, SignatureType type) { + try { + + // make the rename class method accessible + Method renameClassMethod = AttributeInfo.class.getDeclaredMethod("renameClass", Map.class); + renameClassMethod.setAccessible(true); + + for (AttributeInfo attribute : attributes) { + if (attribute instanceof SignatureAttribute) { + // this has to be handled specially because SignatureAttribute.renameClass() is buggy as hell + SignatureAttribute signatureAttribute = (SignatureAttribute)attribute; + String newSignature = type.rename(signatureAttribute.getSignature(), map); + if (newSignature != null) { + signatureAttribute.setSignature(newSignature); + } + } else if (attribute instanceof CodeAttribute) { + // code attributes have signature attributes too (indirectly) + CodeAttribute codeAttribute = (CodeAttribute)attribute; + renameAttributes(codeAttribute.getAttributes(), map, type); + } else if (attribute instanceof LocalVariableTypeAttribute) { + // lvt attributes have signature attributes too + LocalVariableTypeAttribute localVariableAttribute = (LocalVariableTypeAttribute)attribute; + renameLocalVariableTypeAttribute(localVariableAttribute, map); + } else { + renameClassMethod.invoke(attribute, map); + } + } + + } catch(NoSuchMethodException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { + throw new Error("Unable to call javassist methods by reflection!", ex); + } + } + + private static void renameLocalVariableTypeAttribute(LocalVariableTypeAttribute attribute, ReplacerClassMap map) { + + // adapted from LocalVariableAttribute.renameClass() + ConstPool cp = attribute.getConstPool(); + int n = attribute.tableLength(); + byte[] info = attribute.get(); + for (int i = 0; i < n; ++i) { + int pos = i * 10 + 2; + int index = ByteArray.readU16bit(info, pos + 6); + if (index != 0) { + String signature = cp.getUtf8Info(index); + String newSignature = renameLocalVariableSignature(signature, map); + if (newSignature != null) { + ByteArray.write16bit(cp.addUtf8Info(newSignature), info, pos + 6); + } + } + } + } + + private static String renameLocalVariableSignature(String signature, ReplacerClassMap map) { + + // for some reason, signatures with . in them don't count as field signatures + // looks like anonymous classes delimit with . in stead of $ + // convert the . to $, but keep track of how many we replace + // we need to put them back after we translate + int start = signature.lastIndexOf('$') + 1; + int numConverted = 0; + StringBuilder buf = new StringBuilder(signature); + for (int i=buf.length()-1; i>=start; i--) { + char c = buf.charAt(i); + if (c == '.') { + buf.setCharAt(i, '$'); + numConverted++; + } + } + signature = buf.toString(); + + // translate + String newSignature = renameFieldSignature(signature, map); + if (newSignature != null) { + + // put the delimiters back + buf = new StringBuilder(newSignature); + for (int i=buf.length()-1; i>=0 && numConverted > 0; i--) { + char c = buf.charAt(i); + if (c == '$') { + buf.setCharAt(i, '.'); + numConverted--; + } + } + assert(numConverted == 0); + newSignature = buf.toString(); + + return newSignature; + } + + return null; + } + + private static String renameClassSignature(String signature, ReplacerClassMap map) { + try { + ClassSignature type = renameType(SignatureAttribute.toClassSignature(signature), map); + if (type != null) { + return type.encode(); + } + return null; + } catch (BadBytecode ex) { + throw new Error("Can't parse field signature: " + signature); + } + } + + private static String renameFieldSignature(String signature, ReplacerClassMap map) { + try { + ObjectType type = renameType(SignatureAttribute.toFieldSignature(signature), map); + if (type != null) { + return type.encode(); + } + return null; + } catch (BadBytecode ex) { + throw new Error("Can't parse class signature: " + signature); + } + } + + private static String renameMethodSignature(String signature, ReplacerClassMap map) { + try { + MethodSignature type = renameType(SignatureAttribute.toMethodSignature(signature), map); + if (type != null) { + return type.encode(); + } + return null; + } catch (BadBytecode ex) { + throw new Error("Can't parse method signature: " + signature); + } + } + + private static ClassSignature renameType(ClassSignature type, ReplacerClassMap map) { + + TypeParameter[] typeParamTypes = type.getParameters(); + if (typeParamTypes != null) { + typeParamTypes = Arrays.copyOf(typeParamTypes, typeParamTypes.length); + for (int i=0; i m_constructorPool; + + static { + try { + m_getItem = ConstPool.class.getDeclaredMethod("getItem", int.class); + m_getItem.setAccessible(true); + + m_addItem = ConstPool.class.getDeclaredMethod("addItem", Class.forName("javassist.bytecode.ConstInfo")); + m_addItem.setAccessible(true); + + m_addItem0 = ConstPool.class.getDeclaredMethod("addItem0", Class.forName("javassist.bytecode.ConstInfo")); + m_addItem0.setAccessible(true); + + m_items = ConstPool.class.getDeclaredField("items"); + m_items.setAccessible(true); + + m_cache = ConstPool.class.getDeclaredField("itemsCache"); + m_cache.setAccessible(true); + + m_numItems = ConstPool.class.getDeclaredField("numOfItems"); + m_numItems.setAccessible(true); + + m_objects = Class.forName("javassist.bytecode.LongVector").getDeclaredField("objects"); + m_objects.setAccessible(true); + + m_elements = Class.forName("javassist.bytecode.LongVector").getDeclaredField("elements"); + m_elements.setAccessible(true); + + m_methodWritePool = ConstPool.class.getDeclaredMethod("write", DataOutputStream.class); + m_methodWritePool.setAccessible(true); + + m_constructorPool = ConstPool.class.getDeclaredConstructor(DataInputStream.class); + m_constructorPool.setAccessible(true); + } catch (Exception ex) { + throw new Error(ex); + } + } + + private ConstPool m_pool; + + public ConstPoolEditor(ConstPool pool) { + m_pool = pool; + } + + public void writePool(DataOutputStream out) { + try { + m_methodWritePool.invoke(m_pool, out); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public static ConstPool readPool(DataInputStream in) { + try { + return m_constructorPool.newInstance(in); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public String getMemberrefClassname(int memberrefIndex) { + return Descriptor.toJvmName(m_pool.getClassInfo(m_pool.getMemberClass(memberrefIndex))); + } + + public String getMemberrefName(int memberrefIndex) { + return m_pool.getUtf8Info(m_pool.getNameAndTypeName(m_pool.getMemberNameAndType(memberrefIndex))); + } + + public String getMemberrefType(int memberrefIndex) { + return m_pool.getUtf8Info(m_pool.getNameAndTypeDescriptor(m_pool.getMemberNameAndType(memberrefIndex))); + } + + public ConstInfoAccessor getItem(int index) { + try { + Object entry = m_getItem.invoke(m_pool, index); + if (entry == null) { + return null; + } + return new ConstInfoAccessor(entry); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public int addItem(Object item) { + try { + return (Integer)m_addItem.invoke(m_pool, item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public int addItemForceNew(Object item) { + try { + return (Integer)m_addItem0.invoke(m_pool, item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + @SuppressWarnings("rawtypes") + public void removeLastItem() { + try { + // remove the item from the cache + HashMap cache = getCache(); + if (cache != null) { + Object item = getItem(m_pool.getSize() - 1); + cache.remove(item); + } + + // remove the actual item + // based off of LongVector.addElement() + Object items = m_items.get(m_pool); + Object[][] objects = (Object[][])m_objects.get(items); + int numElements = (Integer)m_elements.get(items) - 1; + int nth = numElements >> 7; + int offset = numElements & (128 - 1); + objects[nth][offset] = null; + + // decrement the number of items + m_elements.set(items, numElements); + m_numItems.set(m_pool, (Integer)m_numItems.get(m_pool) - 1); + } catch (Exception ex) { + throw new Error(ex); + } + } + + @SuppressWarnings("rawtypes") + public HashMap getCache() { + try { + return (HashMap)m_cache.get(m_pool); + } catch (Exception ex) { + throw new Error(ex); + } + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + public void changeMemberrefNameAndType(int memberrefIndex, String newName, String newType) { + // NOTE: when changing values, we always need to copy-on-write + try { + // get the memberref item + Object item = getItem(memberrefIndex).getItem(); + + // update the cache + HashMap cache = getCache(); + if (cache != null) { + cache.remove(item); + } + + new MemberRefInfoAccessor(item).setNameAndTypeIndex(m_pool.addNameAndTypeInfo(newName, newType)); + + // update the cache + if (cache != null) { + cache.put(item, item); + } + } catch (Exception ex) { + throw new Error(ex); + } + + // make sure the change worked + assert (newName.equals(getMemberrefName(memberrefIndex))); + assert (newType.equals(getMemberrefType(memberrefIndex))); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + public void changeClassName(int classNameIndex, String newName) { + // NOTE: when changing values, we always need to copy-on-write + try { + // get the class item + Object item = getItem(classNameIndex).getItem(); + + // update the cache + HashMap cache = getCache(); + if (cache != null) { + cache.remove(item); + } + + // add the new name and repoint the name-and-type to it + new ClassInfoAccessor(item).setNameIndex(m_pool.addUtf8Info(newName)); + + // update the cache + if (cache != null) { + cache.put(item, item); + } + } catch (Exception ex) { + throw new Error(ex); + } + } + + public static ConstPool newConstPool() { + // const pool expects the name of a class to initialize itself + // but we want an empty pool + // so give it a bogus name, and then clear the entries afterwards + ConstPool pool = new ConstPool("a"); + + ConstPoolEditor editor = new ConstPoolEditor(pool); + int size = pool.getSize(); + for (int i = 0; i < size - 1; i++) { + editor.removeLastItem(); + } + + // make sure the pool is actually empty + // although, in this case "empty" means one thing in it + // the JVM spec says index 0 should be reserved + assert (pool.getSize() == 1); + assert (editor.getItem(0) == null); + assert (editor.getItem(1) == null); + assert (editor.getItem(2) == null); + assert (editor.getItem(3) == null); + + // also, clear the cache + editor.getCache().clear(); + + return pool; + } + + public String dump() { + StringBuilder buf = new StringBuilder(); + for (int i = 1; i < m_pool.getSize(); i++) { + buf.append(String.format("%4d", i)); + buf.append(" "); + buf.append(getItem(i).toString()); + buf.append("\n"); + } + return buf.toString(); + } +} diff --git a/src/cuchaz/enigma/bytecode/InfoType.java b/src/cuchaz/enigma/bytecode/InfoType.java new file mode 100644 index 0000000..08f2b3e --- /dev/null +++ b/src/cuchaz/enigma/bytecode/InfoType.java @@ -0,0 +1,317 @@ +/******************************************************************************* + * 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.bytecode; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +import cuchaz.enigma.bytecode.accessors.ClassInfoAccessor; +import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor; +import cuchaz.enigma.bytecode.accessors.InvokeDynamicInfoAccessor; +import cuchaz.enigma.bytecode.accessors.MemberRefInfoAccessor; +import cuchaz.enigma.bytecode.accessors.MethodHandleInfoAccessor; +import cuchaz.enigma.bytecode.accessors.MethodTypeInfoAccessor; +import cuchaz.enigma.bytecode.accessors.NameAndTypeInfoAccessor; +import cuchaz.enigma.bytecode.accessors.StringInfoAccessor; + +public enum InfoType { + + Utf8Info( 1, 0 ), + IntegerInfo( 3, 0 ), + FloatInfo( 4, 0 ), + LongInfo( 5, 0 ), + DoubleInfo( 6, 0 ), + ClassInfo( 7, 1 ) { + + @Override + public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { + ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem()); + gatherIndexTree(indices, editor, accessor.getNameIndex()); + } + + @Override + public void remapIndices(Map map, ConstInfoAccessor entry) { + ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem()); + accessor.setNameIndex(remapIndex(map, accessor.getNameIndex())); + } + + @Override + public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { + ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem()); + ConstInfoAccessor nameEntry = pool.getItem(accessor.getNameIndex()); + return nameEntry != null && nameEntry.getTag() == Utf8Info.getTag(); + } + }, + StringInfo( 8, 1 ) { + + @Override + public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { + StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem()); + gatherIndexTree(indices, editor, accessor.getStringIndex()); + } + + @Override + public void remapIndices(Map map, ConstInfoAccessor entry) { + StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem()); + accessor.setStringIndex(remapIndex(map, accessor.getStringIndex())); + } + + @Override + public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { + StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem()); + ConstInfoAccessor stringEntry = pool.getItem(accessor.getStringIndex()); + return stringEntry != null && stringEntry.getTag() == Utf8Info.getTag(); + } + }, + FieldRefInfo( 9, 2 ) { + + @Override + public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { + MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem()); + gatherIndexTree(indices, editor, accessor.getClassIndex()); + gatherIndexTree(indices, editor, accessor.getNameAndTypeIndex()); + } + + @Override + public void remapIndices(Map map, ConstInfoAccessor entry) { + MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem()); + accessor.setClassIndex(remapIndex(map, accessor.getClassIndex())); + accessor.setNameAndTypeIndex(remapIndex(map, accessor.getNameAndTypeIndex())); + } + + @Override + public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { + MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem()); + ConstInfoAccessor classEntry = pool.getItem(accessor.getClassIndex()); + ConstInfoAccessor nameAndTypeEntry = pool.getItem(accessor.getNameAndTypeIndex()); + return classEntry != null && classEntry.getTag() == ClassInfo.getTag() && nameAndTypeEntry != null && nameAndTypeEntry.getTag() == NameAndTypeInfo.getTag(); + } + }, + // same as FieldRefInfo + MethodRefInfo( 10, 2 ) { + + @Override + public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { + FieldRefInfo.gatherIndexTree(indices, editor, entry); + } + + @Override + public void remapIndices(Map map, ConstInfoAccessor entry) { + FieldRefInfo.remapIndices(map, entry); + } + + @Override + public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { + return FieldRefInfo.subIndicesAreValid(entry, pool); + } + }, + // same as FieldRefInfo + InterfaceMethodRefInfo( 11, 2 ) { + + @Override + public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { + FieldRefInfo.gatherIndexTree(indices, editor, entry); + } + + @Override + public void remapIndices(Map map, ConstInfoAccessor entry) { + FieldRefInfo.remapIndices(map, entry); + } + + @Override + public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { + return FieldRefInfo.subIndicesAreValid(entry, pool); + } + }, + NameAndTypeInfo( 12, 1 ) { + + @Override + public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { + NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem()); + gatherIndexTree(indices, editor, accessor.getNameIndex()); + gatherIndexTree(indices, editor, accessor.getTypeIndex()); + } + + @Override + public void remapIndices(Map map, ConstInfoAccessor entry) { + NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem()); + accessor.setNameIndex(remapIndex(map, accessor.getNameIndex())); + accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex())); + } + + @Override + public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { + NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem()); + ConstInfoAccessor nameEntry = pool.getItem(accessor.getNameIndex()); + ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex()); + return nameEntry != null && nameEntry.getTag() == Utf8Info.getTag() && typeEntry != null && typeEntry.getTag() == Utf8Info.getTag(); + } + }, + MethodHandleInfo( 15, 3 ) { + + @Override + public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { + MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem()); + gatherIndexTree(indices, editor, accessor.getTypeIndex()); + gatherIndexTree(indices, editor, accessor.getMethodRefIndex()); + } + + @Override + public void remapIndices(Map map, ConstInfoAccessor entry) { + MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem()); + accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex())); + accessor.setMethodRefIndex(remapIndex(map, accessor.getMethodRefIndex())); + } + + @Override + public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { + MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem()); + ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex()); + ConstInfoAccessor methodRefEntry = pool.getItem(accessor.getMethodRefIndex()); + return typeEntry != null && typeEntry.getTag() == Utf8Info.getTag() && methodRefEntry != null && methodRefEntry.getTag() == MethodRefInfo.getTag(); + } + }, + MethodTypeInfo( 16, 1 ) { + + @Override + public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { + MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem()); + gatherIndexTree(indices, editor, accessor.getTypeIndex()); + } + + @Override + public void remapIndices(Map map, ConstInfoAccessor entry) { + MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem()); + accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex())); + } + + @Override + public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { + MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem()); + ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex()); + return typeEntry != null && typeEntry.getTag() == Utf8Info.getTag(); + } + }, + InvokeDynamicInfo( 18, 2 ) { + + @Override + public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { + InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem()); + gatherIndexTree(indices, editor, accessor.getBootstrapIndex()); + gatherIndexTree(indices, editor, accessor.getNameAndTypeIndex()); + } + + @Override + public void remapIndices(Map map, ConstInfoAccessor entry) { + InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem()); + accessor.setBootstrapIndex(remapIndex(map, accessor.getBootstrapIndex())); + accessor.setNameAndTypeIndex(remapIndex(map, accessor.getNameAndTypeIndex())); + } + + @Override + public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { + InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem()); + ConstInfoAccessor bootstrapEntry = pool.getItem(accessor.getBootstrapIndex()); + ConstInfoAccessor nameAndTypeEntry = pool.getItem(accessor.getNameAndTypeIndex()); + return bootstrapEntry != null && bootstrapEntry.getTag() == Utf8Info.getTag() && nameAndTypeEntry != null && nameAndTypeEntry.getTag() == NameAndTypeInfo.getTag(); + } + }; + + private static Map m_types; + + static { + m_types = Maps.newTreeMap(); + for (InfoType type : values()) { + m_types.put(type.getTag(), type); + } + } + + private int m_tag; + private int m_level; + + private InfoType(int tag, int level) { + m_tag = tag; + m_level = level; + } + + public int getTag() { + return m_tag; + } + + public int getLevel() { + return m_level; + } + + public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { + // by default, do nothing + } + + public void remapIndices(Map map, ConstInfoAccessor entry) { + // by default, do nothing + } + + public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { + // by default, everything is good + return true; + } + + public boolean selfIndexIsValid(ConstInfoAccessor entry, ConstPoolEditor pool) { + ConstInfoAccessor entryCheck = pool.getItem(entry.getIndex()); + if (entryCheck == null) { + return false; + } + return entryCheck.getItem().equals(entry.getItem()); + } + + public static InfoType getByTag(int tag) { + return m_types.get(tag); + } + + public static List getByLevel(int level) { + List types = Lists.newArrayList(); + for (InfoType type : values()) { + if (type.getLevel() == level) { + types.add(type); + } + } + return types; + } + + public static List getSortedByLevel() { + List types = Lists.newArrayList(); + types.addAll(getByLevel(0)); + types.addAll(getByLevel(1)); + types.addAll(getByLevel(2)); + types.addAll(getByLevel(3)); + return types; + } + + public static void gatherIndexTree(Collection indices, ConstPoolEditor editor, int index) { + // add own index + indices.add(index); + + // recurse + ConstInfoAccessor entry = editor.getItem(index); + entry.getType().gatherIndexTree(indices, editor, entry); + } + + private static int remapIndex(Map map, int index) { + Integer newIndex = map.get(index); + if (newIndex == null) { + newIndex = index; + } + return newIndex; + } +} diff --git a/src/cuchaz/enigma/bytecode/InnerClassWriter.java b/src/cuchaz/enigma/bytecode/InnerClassWriter.java new file mode 100644 index 0000000..bdb1b5d --- /dev/null +++ b/src/cuchaz/enigma/bytecode/InnerClassWriter.java @@ -0,0 +1,132 @@ +/******************************************************************************* + * 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.bytecode; + +import java.util.Collection; +import java.util.List; + +import com.google.common.collect.Lists; + +import javassist.CtClass; +import javassist.bytecode.AccessFlag; +import javassist.bytecode.ConstPool; +import javassist.bytecode.EnclosingMethodAttribute; +import javassist.bytecode.InnerClassesAttribute; +import cuchaz.enigma.analysis.JarIndex; +import cuchaz.enigma.mapping.BehaviorEntry; +import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.EntryFactory; + +public class InnerClassWriter { + + private JarIndex m_index; + + public InnerClassWriter(JarIndex index) { + m_index = index; + } + + public void write(CtClass c) { + + // don't change anything if there's already an attribute there + InnerClassesAttribute oldAttr = (InnerClassesAttribute)c.getClassFile().getAttribute(InnerClassesAttribute.tag); + if (oldAttr != null) { + // bail! + return; + } + + ClassEntry obfClassEntry = EntryFactory.getClassEntry(c); + List obfClassChain = m_index.getObfClassChain(obfClassEntry); + + boolean isInnerClass = obfClassChain.size() > 1; + if (isInnerClass) { + + // it's an inner class, rename it to the fully qualified name + c.setName(obfClassEntry.buildClassEntry(obfClassChain).getName()); + + BehaviorEntry caller = m_index.getAnonymousClassCaller(obfClassEntry); + if (caller != null) { + + // write the enclosing method attribute + if (caller.getName().equals("")) { + c.getClassFile().addAttribute(new EnclosingMethodAttribute(c.getClassFile().getConstPool(), caller.getClassName())); + } else { + c.getClassFile().addAttribute(new EnclosingMethodAttribute(c.getClassFile().getConstPool(), caller.getClassName(), caller.getName(), caller.getSignature().toString())); + } + } + } + + // does this class have any inner classes? + Collection obfInnerClassEntries = m_index.getInnerClasses(obfClassEntry); + + if (isInnerClass || !obfInnerClassEntries.isEmpty()) { + + // create an inner class attribute + InnerClassesAttribute attr = new InnerClassesAttribute(c.getClassFile().getConstPool()); + c.getClassFile().addAttribute(attr); + + // write the ancestry, but not the outermost class + for (int i=1; i extendedObfClassChain = Lists.newArrayList(obfClassChain); + extendedObfClassChain.add(obfInnerClassEntry); + + writeInnerClass(attr, extendedObfClassChain, obfInnerClassEntry); + + // update references to use the fully qualified inner class name + c.replaceClassName(obfInnerClassEntry.getName(), obfInnerClassEntry.buildClassEntry(extendedObfClassChain).getName()); + } + } + } + + private void writeInnerClass(InnerClassesAttribute attr, List obfClassChain, ClassEntry obfClassEntry) { + + // get the new inner class name + ClassEntry obfInnerClassEntry = obfClassEntry.buildClassEntry(obfClassChain); + ClassEntry obfOuterClassEntry = obfInnerClassEntry.getOuterClassEntry(); + + // here's what the JVM spec says about the InnerClasses attribute + // append(inner, parent, 0 if anonymous else simple name, flags); + + // update the attribute with this inner class + ConstPool constPool = attr.getConstPool(); + int innerClassIndex = constPool.addClassInfo(obfInnerClassEntry.getName()); + int parentClassIndex = constPool.addClassInfo(obfOuterClassEntry.getName()); + int innerClassNameIndex = 0; + int accessFlags = AccessFlag.PUBLIC; + // TODO: need to figure out if we can put static or not + if (!m_index.isAnonymousClass(obfClassEntry)) { + innerClassNameIndex = constPool.addUtf8Info(obfInnerClassEntry.getInnermostClassName()); + } + + attr.append(innerClassIndex, parentClassIndex, innerClassNameIndex, accessFlags); + + /* DEBUG + System.out.println(String.format("\tOBF: %s -> ATTR: %s,%s,%s (replace %s with %s)", + obfClassEntry, + attr.innerClass(attr.tableLength() - 1), + attr.outerClass(attr.tableLength() - 1), + attr.innerName(attr.tableLength() - 1), + Constants.NonePackage + "/" + obfInnerClassName, + obfClassEntry.getName() + )); + */ + } +} diff --git a/src/cuchaz/enigma/bytecode/LocalVariableRenamer.java b/src/cuchaz/enigma/bytecode/LocalVariableRenamer.java new file mode 100644 index 0000000..ae0455f --- /dev/null +++ b/src/cuchaz/enigma/bytecode/LocalVariableRenamer.java @@ -0,0 +1,123 @@ +/******************************************************************************* + * 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.bytecode; + +import javassist.CtBehavior; +import javassist.CtClass; +import javassist.bytecode.ByteArray; +import javassist.bytecode.CodeAttribute; +import javassist.bytecode.ConstPool; +import javassist.bytecode.LocalVariableAttribute; +import javassist.bytecode.LocalVariableTypeAttribute; +import cuchaz.enigma.mapping.ArgumentEntry; +import cuchaz.enigma.mapping.BehaviorEntry; +import cuchaz.enigma.mapping.EntryFactory; +import cuchaz.enigma.mapping.Translator; + + +public class LocalVariableRenamer { + + private Translator m_translator; + + public LocalVariableRenamer(Translator translator) { + m_translator = translator; + } + + public void rename(CtClass c) { + for (CtBehavior behavior : c.getDeclaredBehaviors()) { + + // if there's a local variable table, just rename everything to v1, v2, v3, ... for now + CodeAttribute codeAttribute = behavior.getMethodInfo().getCodeAttribute(); + if (codeAttribute == null) { + continue; + } + + BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); + ConstPool constants = c.getClassFile().getConstPool(); + + LocalVariableAttribute table = (LocalVariableAttribute)codeAttribute.getAttribute(LocalVariableAttribute.tag); + if (table != null) { + renameLVT(behaviorEntry, constants, table); + } + + LocalVariableTypeAttribute typeTable = (LocalVariableTypeAttribute)codeAttribute.getAttribute(LocalVariableAttribute.typeTag); + if (typeTable != null) { + renameLVTT(typeTable, table); + } + } + } + + // DEBUG + @SuppressWarnings("unused") + private void dumpTable(LocalVariableAttribute table) { + for (int i=0; i names = new ArrayList(numParams); + for (int i = 0; i < numParams; i++) { + names.add(m_translator.translate(new ArgumentEntry(behaviorEntry, i, ""))); + } + + // save the mappings to the class + MethodParametersAttribute.updateClass(behavior.getMethodInfo(), names); + } + } +} diff --git a/src/cuchaz/enigma/bytecode/MethodParametersAttribute.java b/src/cuchaz/enigma/bytecode/MethodParametersAttribute.java new file mode 100644 index 0000000..512e65a --- /dev/null +++ b/src/cuchaz/enigma/bytecode/MethodParametersAttribute.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * 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.bytecode; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import javassist.bytecode.AttributeInfo; +import javassist.bytecode.ConstPool; +import javassist.bytecode.MethodInfo; + +public class MethodParametersAttribute extends AttributeInfo { + + private MethodParametersAttribute(ConstPool pool, List parameterNameIndices) { + super(pool, "MethodParameters", writeStruct(parameterNameIndices)); + } + + public static void updateClass(MethodInfo info, List names) { + + // add the names to the class const pool + ConstPool constPool = info.getConstPool(); + List parameterNameIndices = new ArrayList(); + for (String name : names) { + if (name != null) { + parameterNameIndices.add(constPool.addUtf8Info(name)); + } else { + parameterNameIndices.add(0); + } + } + + // add the attribute to the method + info.addAttribute(new MethodParametersAttribute(constPool, parameterNameIndices)); + } + + private static byte[] writeStruct(List parameterNameIndices) { + // JVM 8 Spec says the struct looks like this: + // http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.24 + // uint8 num_params + // for each param: + // uint16 name_index -> points to UTF8 entry in constant pool, or 0 for no entry + // uint16 access_flags -> don't care, just set to 0 + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream(buf); + + // NOTE: java hates unsigned integers, so we have to be careful here + // the writeShort(), writeByte() methods will read 16,8 low-order bits from the int argument + // as long as the int argument is in range of the unsigned short/byte type, it will be written as an unsigned short/byte + // if the int is out of range, the byte stream won't look the way we want and weird things will happen + final int SIZEOF_UINT8 = 1; + final int SIZEOF_UINT16 = 2; + final int MAX_UINT8 = (1 << 8) - 1; + final int MAX_UINT16 = (1 << 16) - 1; + + try { + assert (parameterNameIndices.size() >= 0 && parameterNameIndices.size() <= MAX_UINT8); + out.writeByte(parameterNameIndices.size()); + + for (Integer index : parameterNameIndices) { + assert (index >= 0 && index <= MAX_UINT16); + out.writeShort(index); + + // just write 0 for the access flags + out.writeShort(0); + } + + out.close(); + byte[] data = buf.toByteArray(); + assert (data.length == SIZEOF_UINT8 + parameterNameIndices.size() * (SIZEOF_UINT16 + SIZEOF_UINT16)); + return data; + } catch (IOException ex) { + throw new Error(ex); + } + } +} diff --git a/src/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java new file mode 100644 index 0000000..9072c29 --- /dev/null +++ b/src/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * 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.bytecode.accessors; + +import java.lang.reflect.Field; + +public class ClassInfoAccessor { + + private static Class m_class; + private static Field m_nameIndex; + + static { + try { + m_class = Class.forName("javassist.bytecode.ClassInfo"); + m_nameIndex = m_class.getDeclaredField("name"); + m_nameIndex.setAccessible(true); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public static boolean isType(ConstInfoAccessor accessor) { + return m_class.isAssignableFrom(accessor.getItem().getClass()); + } + + private Object m_item; + + public ClassInfoAccessor(Object item) { + m_item = item; + } + + public int getNameIndex() { + try { + return (Integer)m_nameIndex.get(m_item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public void setNameIndex(int val) { + try { + m_nameIndex.set(m_item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } +} diff --git a/src/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java new file mode 100644 index 0000000..ede0473 --- /dev/null +++ b/src/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java @@ -0,0 +1,156 @@ +/******************************************************************************* + * 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.bytecode.accessors; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import cuchaz.enigma.bytecode.InfoType; + +public class ConstInfoAccessor { + + private static Class m_class; + private static Field m_index; + private static Method m_getTag; + + static { + try { + m_class = Class.forName("javassist.bytecode.ConstInfo"); + m_index = m_class.getDeclaredField("index"); + m_index.setAccessible(true); + m_getTag = m_class.getMethod("getTag"); + m_getTag.setAccessible(true); + } catch (Exception ex) { + throw new Error(ex); + } + } + + private Object m_item; + + public ConstInfoAccessor(Object item) { + if (item == null) { + throw new IllegalArgumentException("item cannot be null!"); + } + m_item = item; + } + + public ConstInfoAccessor(DataInputStream in) throws IOException { + try { + // read the entry + String className = in.readUTF(); + int oldIndex = in.readInt(); + + // NOTE: ConstInfo instances write a type id (a "tag"), but they don't read it back + // so we have to read it here + in.readByte(); + + Constructor constructor = Class.forName(className).getConstructor(DataInputStream.class, int.class); + constructor.setAccessible(true); + m_item = constructor.newInstance(in, oldIndex); + } catch (IOException ex) { + throw ex; + } catch (Exception ex) { + throw new Error(ex); + } + } + + public Object getItem() { + return m_item; + } + + public int getIndex() { + try { + return (Integer)m_index.get(m_item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public void setIndex(int val) { + try { + m_index.set(m_item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public int getTag() { + try { + return (Integer)m_getTag.invoke(m_item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public ConstInfoAccessor copy() { + return new ConstInfoAccessor(copyItem()); + } + + public Object copyItem() { + // I don't know of a simpler way to copy one of these silly things... + try { + // serialize the item + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream(buf); + write(out); + + // deserialize the item + DataInputStream in = new DataInputStream(new ByteArrayInputStream(buf.toByteArray())); + Object item = new ConstInfoAccessor(in).getItem(); + in.close(); + + return item; + } catch (Exception ex) { + throw new Error(ex); + } + } + + public void write(DataOutputStream out) throws IOException { + try { + out.writeUTF(m_item.getClass().getName()); + out.writeInt(getIndex()); + + Method method = m_item.getClass().getMethod("write", DataOutputStream.class); + method.setAccessible(true); + method.invoke(m_item, out); + } catch (IOException ex) { + throw ex; + } catch (Exception ex) { + throw new Error(ex); + } + } + + @Override + public String toString() { + try { + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + PrintWriter out = new PrintWriter(buf); + Method print = m_item.getClass().getMethod("print", PrintWriter.class); + print.setAccessible(true); + print.invoke(m_item, out); + out.close(); + return buf.toString().replace("\n", ""); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public InfoType getType() { + return InfoType.getByTag(getTag()); + } +} diff --git a/src/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java new file mode 100644 index 0000000..82af0b9 --- /dev/null +++ b/src/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * 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.bytecode.accessors; + +import java.lang.reflect.Field; + +public class InvokeDynamicInfoAccessor { + + private static Class m_class; + private static Field m_bootstrapIndex; + private static Field m_nameAndTypeIndex; + + static { + try { + m_class = Class.forName("javassist.bytecode.InvokeDynamicInfo"); + m_bootstrapIndex = m_class.getDeclaredField("bootstrap"); + m_bootstrapIndex.setAccessible(true); + m_nameAndTypeIndex = m_class.getDeclaredField("nameAndType"); + m_nameAndTypeIndex.setAccessible(true); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public static boolean isType(ConstInfoAccessor accessor) { + return m_class.isAssignableFrom(accessor.getItem().getClass()); + } + + private Object m_item; + + public InvokeDynamicInfoAccessor(Object item) { + m_item = item; + } + + public int getBootstrapIndex() { + try { + return (Integer)m_bootstrapIndex.get(m_item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public void setBootstrapIndex(int val) { + try { + m_bootstrapIndex.set(m_item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public int getNameAndTypeIndex() { + try { + return (Integer)m_nameAndTypeIndex.get(m_item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public void setNameAndTypeIndex(int val) { + try { + m_nameAndTypeIndex.set(m_item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } +} diff --git a/src/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java new file mode 100644 index 0000000..71ee5b7 --- /dev/null +++ b/src/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * 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.bytecode.accessors; + +import java.lang.reflect.Field; + +public class MemberRefInfoAccessor { + + private static Class m_class; + private static Field m_classIndex; + private static Field m_nameAndTypeIndex; + + static { + try { + m_class = Class.forName("javassist.bytecode.MemberrefInfo"); + m_classIndex = m_class.getDeclaredField("classIndex"); + m_classIndex.setAccessible(true); + m_nameAndTypeIndex = m_class.getDeclaredField("nameAndTypeIndex"); + m_nameAndTypeIndex.setAccessible(true); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public static boolean isType(ConstInfoAccessor accessor) { + return m_class.isAssignableFrom(accessor.getItem().getClass()); + } + + private Object m_item; + + public MemberRefInfoAccessor(Object item) { + m_item = item; + } + + public int getClassIndex() { + try { + return (Integer)m_classIndex.get(m_item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public void setClassIndex(int val) { + try { + m_classIndex.set(m_item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public int getNameAndTypeIndex() { + try { + return (Integer)m_nameAndTypeIndex.get(m_item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public void setNameAndTypeIndex(int val) { + try { + m_nameAndTypeIndex.set(m_item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } +} diff --git a/src/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java new file mode 100644 index 0000000..172b0c5 --- /dev/null +++ b/src/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * 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.bytecode.accessors; + +import java.lang.reflect.Field; + +public class MethodHandleInfoAccessor { + + private static Class m_class; + private static Field m_kindIndex; + private static Field m_indexIndex; + + static { + try { + m_class = Class.forName("javassist.bytecode.MethodHandleInfo"); + m_kindIndex = m_class.getDeclaredField("refKind"); + m_kindIndex.setAccessible(true); + m_indexIndex = m_class.getDeclaredField("refIndex"); + m_indexIndex.setAccessible(true); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public static boolean isType(ConstInfoAccessor accessor) { + return m_class.isAssignableFrom(accessor.getItem().getClass()); + } + + private Object m_item; + + public MethodHandleInfoAccessor(Object item) { + m_item = item; + } + + public int getTypeIndex() { + try { + return (Integer)m_kindIndex.get(m_item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public void setTypeIndex(int val) { + try { + m_kindIndex.set(m_item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public int getMethodRefIndex() { + try { + return (Integer)m_indexIndex.get(m_item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public void setMethodRefIndex(int val) { + try { + m_indexIndex.set(m_item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } +} diff --git a/src/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java new file mode 100644 index 0000000..0099a84 --- /dev/null +++ b/src/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * 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.bytecode.accessors; + +import java.lang.reflect.Field; + +public class MethodTypeInfoAccessor { + + private static Class m_class; + private static Field m_descriptorIndex; + + static { + try { + m_class = Class.forName("javassist.bytecode.MethodTypeInfo"); + m_descriptorIndex = m_class.getDeclaredField("descriptor"); + m_descriptorIndex.setAccessible(true); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public static boolean isType(ConstInfoAccessor accessor) { + return m_class.isAssignableFrom(accessor.getItem().getClass()); + } + + private Object m_item; + + public MethodTypeInfoAccessor(Object item) { + m_item = item; + } + + public int getTypeIndex() { + try { + return (Integer)m_descriptorIndex.get(m_item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public void setTypeIndex(int val) { + try { + m_descriptorIndex.set(m_item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } +} diff --git a/src/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java new file mode 100644 index 0000000..3ecc129 --- /dev/null +++ b/src/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * 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.bytecode.accessors; + +import java.lang.reflect.Field; + +public class NameAndTypeInfoAccessor { + + private static Class m_class; + private static Field m_nameIndex; + private static Field m_typeIndex; + + static { + try { + m_class = Class.forName("javassist.bytecode.NameAndTypeInfo"); + m_nameIndex = m_class.getDeclaredField("memberName"); + m_nameIndex.setAccessible(true); + m_typeIndex = m_class.getDeclaredField("typeDescriptor"); + m_typeIndex.setAccessible(true); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public static boolean isType(ConstInfoAccessor accessor) { + return m_class.isAssignableFrom(accessor.getItem().getClass()); + } + + private Object m_item; + + public NameAndTypeInfoAccessor(Object item) { + m_item = item; + } + + public int getNameIndex() { + try { + return (Integer)m_nameIndex.get(m_item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public void setNameIndex(int val) { + try { + m_nameIndex.set(m_item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public int getTypeIndex() { + try { + return (Integer)m_typeIndex.get(m_item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public void setTypeIndex(int val) { + try { + m_typeIndex.set(m_item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } +} diff --git a/src/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java new file mode 100644 index 0000000..f150612 --- /dev/null +++ b/src/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * 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.bytecode.accessors; + +import java.lang.reflect.Field; + +public class StringInfoAccessor { + + private static Class m_class; + private static Field m_stringIndex; + + static { + try { + m_class = Class.forName("javassist.bytecode.StringInfo"); + m_stringIndex = m_class.getDeclaredField("string"); + m_stringIndex.setAccessible(true); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public static boolean isType(ConstInfoAccessor accessor) { + return m_class.isAssignableFrom(accessor.getItem().getClass()); + } + + private Object m_item; + + public StringInfoAccessor(Object item) { + m_item = item; + } + + public int getStringIndex() { + try { + return (Integer)m_stringIndex.get(m_item); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public void setStringIndex(int val) { + try { + m_stringIndex.set(m_item, val); + } catch (Exception ex) { + throw new Error(ex); + } + } +} diff --git a/src/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java new file mode 100644 index 0000000..38e8ff9 --- /dev/null +++ b/src/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * 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.bytecode.accessors; + +public class Utf8InfoAccessor { + + private static Class m_class; + + static { + try { + m_class = Class.forName("javassist.bytecode.Utf8Info"); + } catch (Exception ex) { + throw new Error(ex); + } + } + + public static boolean isType(ConstInfoAccessor accessor) { + return m_class.isAssignableFrom(accessor.getItem().getClass()); + } +} -- cgit v1.2.3