package immibis.bon; import immibis.bon.ClassReferenceData.FieldData; import immibis.bon.ClassReferenceData.MethodData; import immibis.bon.org.objectweb.asm.Opcodes; import immibis.bon.org.objectweb.asm.Type; import immibis.bon.org.objectweb.asm.tree.AbstractInsnNode; import immibis.bon.org.objectweb.asm.tree.AnnotationNode; import immibis.bon.org.objectweb.asm.tree.ClassNode; import immibis.bon.org.objectweb.asm.tree.FieldInsnNode; import immibis.bon.org.objectweb.asm.tree.FieldNode; import immibis.bon.org.objectweb.asm.tree.FrameNode; import immibis.bon.org.objectweb.asm.tree.InnerClassNode; import immibis.bon.org.objectweb.asm.tree.LdcInsnNode; import immibis.bon.org.objectweb.asm.tree.LocalVariableNode; import immibis.bon.org.objectweb.asm.tree.MethodInsnNode; import immibis.bon.org.objectweb.asm.tree.MethodNode; import immibis.bon.org.objectweb.asm.tree.MultiANewArrayInsnNode; import immibis.bon.org.objectweb.asm.tree.TryCatchBlockNode; import immibis.bon.org.objectweb.asm.tree.TypeInsnNode; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; public class Remapper { // returns actual owner of field // or null if the field could not be resolved private static String resolveField(Map<String, ClassReferenceData> refClasses, String owner, String name, String desc, Mapping m) { ClassReferenceData cn = refClasses.get(owner); if(cn == null) { return null; } // http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-5.html#jvms-5.4.3.2 for(FieldData fn : cn.fields) if(fn.name.equals(name) && fn.desc.equals(desc)) { return owner; } for(String i : cn.interfaces) { String result = resolveField(refClasses, i, name, desc, m); if(result != null) { return result; } } return resolveField(refClasses, cn.superName, name, desc, m); } // returns [realOwner, realDesc] // or null if the method could not be resolved private static String[] resolveMethod(Map<String, ClassReferenceData> refClasses, String owner, String name, String desc, Mapping m) { ClassReferenceData cn = refClasses.get(owner); if(cn == null) return null; if((cn.access & Opcodes.ACC_INTERFACE) != 0) { // interface method resolution; http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-5.html#jvms-5.4.3.4 for(MethodData mn : cn.methods) { if(mn.name.equals(name) && mn.desc.equals(desc) && !m.getMethod(owner, name, desc).equals(name)) return new String[] {owner, desc}; } for(String i : cn.interfaces) { String[] result = resolveMethod(refClasses, i, name, desc, m); if(result != null) return result; } return null; } else { // normal method resolution; http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-5.html#jvms-5.4.3.3 String originalOwner = owner; while(true) { cn = refClasses.get(owner); if(cn == null) break; for(MethodData mn : cn.methods) { if(mn.name.equals(name) && mn.desc.equals(desc) && !m.getMethod(owner, name, desc).equals(name)) return new String[] {owner, desc}; } owner = cn.superName; } owner = originalOwner; while(true) { cn = refClasses.get(owner); if(cn == null) break; for(String i : cn.interfaces) { String[] result = resolveMethod(refClasses, i, name, desc, m); if(result != null) return result; } owner = cn.superName; } return null; } } public static ClassCollection remap(ClassCollection cc, Mapping m, Collection<ReferenceDataCollection> refs, IProgressListener progress) { if(!cc.getNameSet().equals(m.fromNS)) throw new IllegalArgumentException("Input classes use nameset "+cc.getNameSet()+", but mapping is from "+m.fromNS+"; cannot apply mapping"); for(ReferenceDataCollection ref : refs) if(!ref.getNameSet().equals(m.fromNS)) throw new IllegalArgumentException("Reference ClassCollection uses nameset "+ref.getNameSet()+" but input uses "+m.fromNS); HashMap<String, ClassReferenceData> refClasses = new HashMap<>(); for(ReferenceDataCollection refcc : refs) for(ClassReferenceData cn : refcc.getAllClasses()) refClasses.put(cn.name, cn); for(ClassNode cn : cc.getAllClasses()) refClasses.put(cn.name, ClassReferenceData.fromClassNode(cn)); cc = cc.cloneWithNameSet(m.toNS); int classesProcessed = 0; if(progress != null) progress.setMax(cc.getAllClasses().size()); for(ClassNode cn : cc.getAllClasses()) { if(progress != null) progress.set(classesProcessed++); for(MethodNode mn : cn.methods) { String[] resolvedMN = resolveMethod(refClasses, cn.name, mn.name, mn.desc, m); if(resolvedMN != null) { mn.name = m.getMethod(resolvedMN[0], mn.name, resolvedMN[1]); mn.desc = m.mapMethodDescriptor(resolvedMN[1]); } else { mn.name = m.getMethod(cn.name, mn.name, mn.desc); mn.desc = m.mapMethodDescriptor(mn.desc); } if(mn.instructions != null) { for(AbstractInsnNode ain = mn.instructions.getFirst(); ain != null; ain = ain.getNext()) { if(ain instanceof FieldInsnNode) { FieldInsnNode fin = (FieldInsnNode)ain; String realOwner = resolveField(refClasses, fin.owner, fin.name, fin.desc, m); if(realOwner == null) realOwner = fin.owner; fin.name = m.getField(realOwner, fin.name); fin.desc = m.mapTypeDescriptor(fin.desc); fin.owner = m.getClass(realOwner); } else if(ain instanceof FrameNode) { FrameNode fn = (FrameNode)ain; if(fn.local != null) for(int k = 0; k < fn.local.size(); k++) if(fn.local.get(k) instanceof String) fn.local.set(k, m.getClass((String)fn.local.get(k))); if(fn.stack != null) for(int k = 0; k < fn.stack.size(); k++) if(fn.stack.get(k) instanceof String) fn.stack.set(k, m.getClass((String)fn.stack.get(k))); } else if(ain instanceof MethodInsnNode) { MethodInsnNode min = (MethodInsnNode)ain; String[] realOwnerAndDesc = resolveMethod(refClasses, min.owner, min.name, min.desc, m); String realOwner = realOwnerAndDesc == null ? min.owner : realOwnerAndDesc[0]; String realDesc = realOwnerAndDesc == null ? min.desc : realOwnerAndDesc[1]; min.name = m.getMethod(realOwner, min.name, realDesc); min.owner = m.getClass(min.owner); // note: not realOwner which could be an interface min.desc = m.mapMethodDescriptor(realDesc); } else if(ain instanceof LdcInsnNode) { LdcInsnNode lin = (LdcInsnNode)ain; if(lin.cst instanceof Type) { lin.cst = Type.getType(m.mapTypeDescriptor(((Type)lin.cst).getDescriptor())); } } else if(ain instanceof TypeInsnNode) { TypeInsnNode tin = (TypeInsnNode)ain; tin.desc = m.getClass(tin.desc); } else if(ain instanceof MultiANewArrayInsnNode) { MultiANewArrayInsnNode min = (MultiANewArrayInsnNode)ain; min.desc = m.getClass(min.desc); } } } processAnnotationList(m, mn.visibleAnnotations); processAnnotationList(m, mn.visibleParameterAnnotations); processAnnotationList(m, mn.invisibleAnnotations); processAnnotationList(m, mn.invisibleParameterAnnotations); for(TryCatchBlockNode tcb : mn.tryCatchBlocks) { if(tcb.type != null) tcb.type = m.getClass(tcb.type); } { Set<String> exceptions = new HashSet<>(mn.exceptions); exceptions.addAll(m.getExceptions(cn.name, mn.name, mn.desc)); mn.exceptions.clear(); for(String s : exceptions) mn.exceptions.add(m.getClass(s)); } if(mn.localVariables != null) for(LocalVariableNode lvn : mn.localVariables) lvn.desc = m.mapTypeDescriptor(lvn.desc); // TODO: support signatures (for generics, even though Minecraft doesn't use them after obfuscation) } for(FieldNode fn : cn.fields) { fn.name = m.getField(cn.name, fn.name); fn.desc = m.mapTypeDescriptor(fn.desc); processAnnotationList(m, fn.invisibleAnnotations); processAnnotationList(m, fn.visibleAnnotations); // TODO: support signatures (for generics, even though Minecraft doesn't use them after obfuscation) } cn.name = m.getClass(cn.name); cn.superName = m.getClass(cn.superName); for(int k = 0; k < cn.interfaces.size(); k++) cn.interfaces.set(k, m.getClass(cn.interfaces.get(k))); processAnnotationList(m, cn.invisibleAnnotations); processAnnotationList(m, cn.visibleAnnotations); // TODO: support signatures (for generics, even though Minecraft doesn't use them after obfuscation) for(InnerClassNode icn : cn.innerClasses) { icn.name = m.getClass(icn.name); if(icn.outerName != null) icn.outerName = m.getClass(icn.outerName); } if(cn.outerMethod != null) { String[] resolved = resolveMethod(refClasses, cn.outerClass, cn.outerMethod, cn.outerMethodDesc, m); if(resolved != null) { cn.outerMethod = m.getMethod(resolved[0], cn.outerMethod, resolved[1]); cn.outerMethodDesc = m.mapMethodDescriptor(resolved[1]); } else { cn.outerMethod = m.getMethod(cn.outerClass, cn.outerMethod, cn.outerMethodDesc); cn.outerMethodDesc = m.mapMethodDescriptor(cn.outerMethodDesc); } } if(cn.outerClass != null) cn.outerClass = m.getClass(cn.outerClass); } return cc; } private static void processAnnotationList(Mapping m, List<AnnotationNode>[] array) { if(array != null) for(List<AnnotationNode> list : array) processAnnotationList(m, list); } private static void processAnnotationList(Mapping m, List<AnnotationNode> list) { if(list != null) for(AnnotationNode an : list) processAnnotation(m, an); } private static void processAnnotation(Mapping m, AnnotationNode an) { an.desc = m.getClass(an.desc); if(an.values != null) for(int k = 1; k < an.values.size(); k += 2) an.values.set(k, processAnnotationValue(m, an.values.get(k))); } private static Object processAnnotationValue(Mapping m, Object value) { if(value instanceof Type) return Type.getType(m.getClass(((Type)value).getDescriptor())); if(value instanceof String[]) { // enum value; need to remap both the enum, and the value String[] array = (String[])value; String desc = array[0], enumvalue = array[1]; if(!desc.startsWith("L") || !desc.endsWith(";")) throw new AssertionError("Not a class type descriptor: "+desc); return new String[] {m.getClass(desc), m.getField(desc.substring(1, desc.length() - 1), enumvalue)}; } if(value instanceof List) { @SuppressWarnings("unchecked") List<Object> list = (List<Object>)value; for(int k = 0; k < list.size(); k++) list.set(k, processAnnotationValue(m, list.get(k))); return value; } if(value instanceof AnnotationNode) { processAnnotation(m, (AnnotationNode)value); return value; } return value; } /*public static ClassCollection remap(ClassCollection classes, NameSet toNS, Collection<ClassCollection> refs, IProgressListener progress) throws MappingUnavailableException, IOException { return remap(classes, MappingFactory.getMapping(classes.getNameSet(), toNS, null), refs, progress); }*/ }