diff --git a/src/main/java/net/fabricmc/stitch/commands/GenState.java b/src/main/java/net/fabricmc/stitch/commands/GenState.java index beab5d8..a3f8d66 100644 --- a/src/main/java/net/fabricmc/stitch/commands/GenState.java +++ b/src/main/java/net/fabricmc/stitch/commands/GenState.java @@ -117,7 +117,8 @@ public void generate(File file, JarRootEntry jarEntry, JarRootEntry jarOld) thro } public static boolean isMappedClass(ClassStorage storage, JarClassEntry c) { - return !c.isAnonymous(); + // if an anonymous class' name does not follow the $ convention, we give it a new name anyway + return !(c.isAnonymous() && c.getName().startsWith(c.getEnclosingClassName() + "$")); } public static boolean isMappedField(ClassStorage storage, JarClassEntry c, JarFieldEntry f) { @@ -143,7 +144,7 @@ private String getFieldName(ClassStorage storage, JarClassEntry c, JarFieldEntry } if (newToIntermediary != null) { - EntryTriple findEntry = newToIntermediary.getField(c.getFullyQualifiedName(), f.getName(), f.getDescriptor()); + EntryTriple findEntry = newToIntermediary.getField(c.getName(), f.getName(), f.getDescriptor()); if (findEntry != null) { if (findEntry.getName().contains("field_")) { return findEntry.getName(); @@ -156,7 +157,7 @@ private String getFieldName(ClassStorage storage, JarClassEntry c, JarFieldEntry } if (newToOld != null) { - EntryTriple findEntry = newToOld.getField(c.getFullyQualifiedName(), f.getName(), f.getDescriptor()); + EntryTriple findEntry = newToOld.getField(c.getName(), f.getName(), f.getDescriptor()); if (findEntry != null) { findEntry = oldToIntermediary.getField(findEntry); if (findEntry != null) { @@ -181,7 +182,7 @@ private String getPropagation(ClassStorage storage, JarClassEntry classEntry) { return ""; } - StringBuilder builder = new StringBuilder(classEntry.getFullyQualifiedName()); + StringBuilder builder = new StringBuilder(classEntry.getName()); List strings = new ArrayList<>(); String scs = getPropagation(storage, classEntry.getSuperClass(storage)); if (!scs.isEmpty()) { @@ -240,14 +241,14 @@ private void findNames(ClassStorage storageOld, ClassStorage storageNew, JarClas for (JarClassEntry cc : ccList) { EntryTriple findEntry = null; if (newToIntermediary != null) { - findEntry = newToIntermediary.getMethod(cc.getFullyQualifiedName(), m.getName(), m.getDescriptor()); + findEntry = newToIntermediary.getMethod(cc.getName(), m.getName(), m.getDescriptor()); if (findEntry != null) { names.computeIfAbsent(findEntry.getName(), (s) -> new TreeSet<>()).add(getNamesListEntry(storageNew, cc) + suffix); } } if (findEntry == null && newToOld != null) { - findEntry = newToOld.getMethod(cc.getFullyQualifiedName(), m.getName(), m.getDescriptor()); + findEntry = newToOld.getMethod(cc.getName(), m.getName(), m.getDescriptor()); if (findEntry != null) { EntryTriple newToOldEntry = findEntry; findEntry = oldToIntermediary.getMethod(newToOldEntry); @@ -255,13 +256,13 @@ private void findNames(ClassStorage storageOld, ClassStorage storageNew, JarClas names.computeIfAbsent(findEntry.getName(), (s) -> new TreeSet<>()).add(getNamesListEntry(storageNew, cc) + suffix); } else { // more involved... - JarClassEntry oldBase = storageOld.getClass(newToOldEntry.getOwner(), false); + JarClassEntry oldBase = storageOld.getClass(newToOldEntry.getOwner(), null); if (oldBase != null) { JarMethodEntry oldM = oldBase.getMethod(newToOldEntry.getName() + newToOldEntry.getDesc()); List cccList = oldM.getMatchingEntries(storageOld, oldBase); for (JarClassEntry ccc : cccList) { - findEntry = oldToIntermediary.getMethod(ccc.getFullyQualifiedName(), oldM.getName(), oldM.getDescriptor()); + findEntry = oldToIntermediary.getMethod(ccc.getName(), oldM.getName(), oldM.getDescriptor()); if (findEntry != null) { names.computeIfAbsent(findEntry.getName(), (s) -> new TreeSet<>()).add(getNamesListEntry(storageOld, ccc) + suffix); } @@ -349,21 +350,44 @@ private String getMethodName(ClassStorage storageOld, ClassStorage storageNew, J } private void addClass(BufferedWriter writer, JarClassEntry c, ClassStorage storageOld, ClassStorage storage, String translatedPrefix) throws IOException { - String className = c.getName(); + String fullName = c.getName(); + // Typically inner class names are of the form com/example/Example$InnerName + // but this is not required by the JVM spec! However, for the names we generate + // we do follow this convention. + if (c.isLocal()) { + String enclName = c.getEnclosingClassName(); + String innerName = c.getInnerName(); + // typically, local classes' full names have a number prefix + // before the inner name part - this is useful for allowing + // multiple local classes to have the same inner name, so we add it + if (fullName.startsWith(enclName + "$") && fullName.endsWith(innerName)) { + translatedPrefix += fullName.substring(enclName.length() + 1, fullName.length() - innerName.length()); + } + } String cname = ""; String prefixSaved = translatedPrefix; - if(!this.obfuscatedPatterns.stream().anyMatch(p -> p.matcher(className).matches())) { - translatedPrefix = c.getFullyQualifiedName(); + if(!this.obfuscatedPatterns.stream().anyMatch(p -> p.matcher(fullName).matches())) { + translatedPrefix = fullName; } else { if (!isMappedClass(storage, c)) { - cname = c.getName(); + if (c.isAnonymous()) { + // anonymous classes are only unmapped if their name + // follows the standard $ convention + cname = fullName.substring(c.getEnclosingClassName().length() + 1); + } else { + // throw exception in case the impl of isMappedClass + // changes but we forget to deal with it here + throw new IllegalStateException("don't know what to do with class " + fullName); + } } else { cname = null; if (newToIntermediary != null) { - String findName = newToIntermediary.getClass(c.getFullyQualifiedName()); + String findName = newToIntermediary.getClass(fullName); if (findName != null) { + // the names we generate follow the $ convention for inner class names, + // so we can safely find the inner name like this String[] r = findName.split("\\$"); cname = r[r.length - 1]; if (r.length == 1) { @@ -373,10 +397,11 @@ private void addClass(BufferedWriter writer, JarClassEntry c, ClassStorage stora } if (cname == null && newToOld != null) { - String findName = newToOld.getClass(c.getFullyQualifiedName()); + String findName = newToOld.getClass(fullName); if (findName != null) { findName = oldToIntermediary.getClass(findName); if (findName != null) { + // for the same reason as above, we can safely find the inner name like this String[] r = findName.split("\\$"); cname = r[r.length - 1]; if (r.length == 1) { @@ -400,7 +425,7 @@ private void addClass(BufferedWriter writer, JarClassEntry c, ClassStorage stora } } - writer.write("CLASS\t" + c.getFullyQualifiedName() + "\t" + translatedPrefix + cname + "\n"); + writer.write("CLASS\t" + fullName + "\t" + translatedPrefix + cname + "\n"); for (JarFieldEntry f : c.getFields()) { String fName = getFieldName(storage, c, f); @@ -409,7 +434,7 @@ private void addClass(BufferedWriter writer, JarClassEntry c, ClassStorage stora } if (fName != null) { - writer.write("FIELD\t" + c.getFullyQualifiedName() + writer.write("FIELD\t" + fullName + "\t" + f.getDescriptor() + "\t" + f.getName() + "\t" + fName + "\n"); @@ -425,7 +450,7 @@ private void addClass(BufferedWriter writer, JarClassEntry c, ClassStorage stora } if (mName != null) { - writer.write("METHOD\t" + c.getFullyQualifiedName() + writer.write("METHOD\t" + fullName + "\t" + m.getDescriptor() + "\t" + m.getName() + "\t" + mName + "\n"); diff --git a/src/main/java/net/fabricmc/stitch/representation/ClassStorage.java b/src/main/java/net/fabricmc/stitch/representation/ClassStorage.java index a06f72e..0c3d756 100644 --- a/src/main/java/net/fabricmc/stitch/representation/ClassStorage.java +++ b/src/main/java/net/fabricmc/stitch/representation/ClassStorage.java @@ -17,5 +17,5 @@ package net.fabricmc.stitch.representation; public interface ClassStorage { - JarClassEntry getClass(String name, boolean create); + JarClassEntry getClass(String name, JarClassEntry.Populator populator); } diff --git a/src/main/java/net/fabricmc/stitch/representation/JarClassEntry.java b/src/main/java/net/fabricmc/stitch/representation/JarClassEntry.java index 90a0074..22a08e1 100644 --- a/src/main/java/net/fabricmc/stitch/representation/JarClassEntry.java +++ b/src/main/java/net/fabricmc/stitch/representation/JarClassEntry.java @@ -23,7 +23,6 @@ import java.util.stream.Collectors; public class JarClassEntry extends AbstractJarEntry { - String fullyQualifiedName; final Map innerClasses; final Map fields; final Map methods; @@ -34,11 +33,17 @@ public class JarClassEntry extends AbstractJarEntry { List interfaces; List subclasses; List implementers; - - protected JarClassEntry(String name, String fullyQualifiedName) { + /** outer class for inner classes */ + String declaringClass; + /** outer class for anonymous and local classes */ + String enclosingClass; + String enclosingMethod; + String enclosingMethodDesc; + String innerName; + + protected JarClassEntry(String name) { super(name); - this.fullyQualifiedName = fullyQualifiedName; this.innerClasses = new TreeMap<>(Comparator.naturalOrder()); this.fields = new TreeMap<>(Comparator.naturalOrder()); this.methods = new TreeMap<>(Comparator.naturalOrder()); @@ -48,24 +53,40 @@ protected JarClassEntry(String name, String fullyQualifiedName) { this.implementers = new ArrayList<>(); } - protected void populate(int access, String signature, String superclass, String[] interfaces) { - this.setAccess(access); - this.signature = signature; - this.superclass = superclass; - this.interfaces = Arrays.asList(interfaces); + protected void populate(Populator populator) { + this.setAccess(populator.access); + this.signature = populator.signature; + this.superclass = populator.superclass; + this.interfaces = Arrays.asList(populator.interfaces); + if (populator.nested) { + this.declaringClass = populator.declaringClass; + this.enclosingClass = populator.enclosingClass; + this.enclosingMethod = populator.enclosingMethod; + this.enclosingMethodDesc = populator.enclosingMethodDesc; + this.innerName = populator.innerName; + } } - protected void populateParents(ClassStorage storage) { + protected void populateRelations(ClassStorage storage) { JarClassEntry superEntry = getSuperClass(storage); if (superEntry != null) { - superEntry.subclasses.add(fullyQualifiedName); + superEntry.subclasses.add(name); } for (JarClassEntry itf : getInterfaces(storage)) { if (itf != null) { - itf.implementers.add(fullyQualifiedName); + itf.implementers.add(name); } } + + JarClassEntry declaringEntry = getDeclaringClass(storage); + if (declaringEntry != null) { + declaringEntry.innerClasses.put(name, this); + } + JarClassEntry enclosingEntry = getEnclosingClass(storage); + if (enclosingEntry != null) { + enclosingEntry.innerClasses.put(name, this); + } } // unstable @@ -74,10 +95,6 @@ public Collection> getRelatedMethods(JarMethodEntry return relatedMethods.getOrDefault(m.getKey(), Collections.EMPTY_SET); } - public String getFullyQualifiedName() { - return fullyQualifiedName; - } - public String getSignature() { return signature; } @@ -87,7 +104,7 @@ public String getSuperClassName() { } public JarClassEntry getSuperClass(ClassStorage storage) { - return storage.getClass(superclass, false); + return storage.getClass(superclass, null); } public List getInterfaceNames() { @@ -120,11 +137,43 @@ private List toClassEntryList(ClassStorage storage, List } return stringList.stream() - .map((s) -> storage.getClass(s, false)) + .map((s) -> storage.getClass(s, null)) .filter(Objects::nonNull) .collect(Collectors.toList()); } + public String getDeclaringClassName() { + return declaringClass; + } + + public JarClassEntry getDeclaringClass(ClassStorage storage) { + return hasDeclaringClass() ? storage.getClass(declaringClass, null) : null; + } + + public String getEnclosingClassName() { + return enclosingClass; + } + + public JarClassEntry getEnclosingClass(ClassStorage storage) { + return hasEnclosingClass() ? storage.getClass(enclosingClass, null) : null; + } + + public String getEnclosingMethodName() { + return enclosingMethod; + } + + public String getEnclosingMethodDescriptor() { + return enclosingMethodDesc; + } + + public JarMethodEntry getEnclosingMethod(JarRootEntry storage) { + return hasEnclosingClass() && hasEnclosingMethod() ? getEnclosingClass(storage).getMethod(enclosingMethod + enclosingMethodDesc) : null; + } + + public String getInnerName() { + return innerName; + } + public JarClassEntry getInnerClass(String name) { return innerClasses.get(name); } @@ -153,20 +202,29 @@ public boolean isInterface() { return Access.isInterface(getAccess()); } + public boolean hasDeclaringClass() { + return declaringClass != null; + } + + public boolean hasEnclosingClass() { + return enclosingClass != null; + } + + public boolean hasEnclosingMethod() { + return enclosingMethod != null; + } + public boolean isAnonymous() { - return getName().matches("[0-9]+"); + return hasEnclosingClass() && innerName == null; } - @Override - public String getKey() { - return getFullyQualifiedName(); + public boolean isLocal() { + return hasEnclosingClass() && innerName != null; } public void remap(Remapper remapper) { - String oldName = fullyQualifiedName; - fullyQualifiedName = remapper.map(fullyQualifiedName); - String[] s = fullyQualifiedName.split("\\$"); - name = s[s.length - 1]; + String oldName = name; + name = remapper.map(name); if (superclass != null) { superclass = remapper.map(superclass); @@ -209,4 +267,27 @@ public void remap(Remapper remapper) { relatedMethods.put(methodKeyRemaps.getOrDefault(entry.getKey(), entry.getKey()), entry.getValue()); } } + + public static class Populator { + public int access; + public String name; + public String signature; + public String superclass; + public String[] interfaces; + public String declaringClass; + public String enclosingClass; + public String enclosingMethod; + public String enclosingMethodDesc; + public String innerName; + + boolean nested; + + public Populator(int access, String name, String signature, String superclass, String[] interfaces) { + this.access = access; + this.name = name; + this.signature = signature; + this.superclass = superclass; + this.interfaces = interfaces; + } + } } diff --git a/src/main/java/net/fabricmc/stitch/representation/JarReader.java b/src/main/java/net/fabricmc/stitch/representation/JarReader.java index 4f4c048..2729f46 100644 --- a/src/main/java/net/fabricmc/stitch/representation/JarReader.java +++ b/src/main/java/net/fabricmc/stitch/representation/JarReader.java @@ -61,7 +61,9 @@ public JarReader(JarRootEntry jar) { } private class VisitorClass extends ClassVisitor { - private JarClassEntry entry; + private JarClassEntry.Populator populator; + private Set fields; + private Set methods; public VisitorClass(int api, ClassVisitor classVisitor) { super(api, classVisitor); @@ -70,132 +72,69 @@ public VisitorClass(int api, ClassVisitor classVisitor) { @Override public void visit(final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces) { - this.entry = jar.getClass(name, true); - this.entry.populate(access, signature, superName, interfaces); + this.populator = new JarClassEntry.Populator(access, name, signature, superName, interfaces); + this.fields = new LinkedHashSet<>(); + this.methods = new LinkedHashSet<>(); super.visit(version, access, name, signature, superName, interfaces); } @Override - public FieldVisitor visitField(final int access, final String name, final String descriptor, - final String signature, final Object value) { - JarFieldEntry field = new JarFieldEntry(access, name, descriptor, signature); - this.entry.fields.put(field.getKey(), field); + public void visitOuterClass(String owner, String name, String descriptor) { + // attribute for the outer class and method, + // used by anonymous and local classes + populator.enclosingClass = owner; + populator.enclosingMethod = name; + populator.enclosingMethodDesc = descriptor; - return new VisitorField(api, super.visitField(access, name, descriptor, signature, value), - entry, field); + super.visitOuterClass(owner, name, descriptor); } @Override - public MethodVisitor visitMethod(final int access, final String name, final String descriptor, - final String signature, final String[] exceptions) { - JarMethodEntry method = new JarMethodEntry(access, name, descriptor, signature); - this.entry.methods.put(method.getKey(), method); - - return new VisitorMethod(api, super.visitMethod(access, name, descriptor, signature, exceptions), - entry, method); - } - } - - private class VisitorClassStageTwo extends ClassVisitor { - private JarClassEntry entry; + public void visitInnerClass(String name, String outerName, String innerName, int access) { + // the inner class attributes can be added for any + // inner class referenced from within this class + if (populator.name.equals(name)) { + // While the outer class attribute is only present + // for anonymous and local classes, the inner class + // attribute is present for all nested classes, thus + // we only mark a class as nested if this attribute + // is present. + populator.nested = true; + populator.declaringClass = outerName; + populator.innerName = innerName; + } - public VisitorClassStageTwo(int api, ClassVisitor classVisitor) { - super(api, classVisitor); + super.visitInnerClass(name, outerName, innerName, access); } @Override - public void visit(final int version, final int access, final String name, final String signature, - final String superName, final String[] interfaces) { - this.entry = jar.getClass(name, true); - super.visit(version, access, name, signature, superName, interfaces); + public FieldVisitor visitField(final int access, final String name, final String descriptor, + final String signature, final Object value) { + this.fields.add(new JarFieldEntry(access, name, descriptor, signature)); + + return super.visitField(access, name, descriptor, signature, value); } @Override public MethodVisitor visitMethod(final int access, final String name, final String descriptor, final String signature, final String[] exceptions) { - JarMethodEntry method = new JarMethodEntry(access, name, descriptor, signature); - this.entry.methods.put(method.getKey(), method); - - if ((access & (Opcodes.ACC_BRIDGE | Opcodes.ACC_SYNTHETIC)) != 0) { - return new VisitorBridge(api, access, super.visitMethod(access, name, descriptor, signature, exceptions), - entry, method); - } else { - return super.visitMethod(access, name, descriptor, signature, exceptions); - } - } - } - - private class VisitorField extends FieldVisitor { - private final JarClassEntry classEntry; - private final JarFieldEntry entry; + this.methods.add(new JarMethodEntry(access, name, descriptor, signature)); - public VisitorField(int api, FieldVisitor fieldVisitor, JarClassEntry classEntry, JarFieldEntry entry) { - super(api, fieldVisitor); - this.classEntry = classEntry; - this.entry = entry; - } - } - - private static class MethodRef { - final String owner, name, descriptor; - - MethodRef(String owner, String name, String descriptor) { - this.owner = owner; - this.name = name; - this.descriptor = descriptor; - } - } - - private class VisitorBridge extends VisitorMethod { - private final boolean hasBridgeFlag; - private final List methodRefs = new ArrayList<>(); - - public VisitorBridge(int api, int access, MethodVisitor methodVisitor, JarClassEntry classEntry, JarMethodEntry entry) { - super(api, methodVisitor, classEntry, entry); - hasBridgeFlag = ((access & Opcodes.ACC_BRIDGE) != 0); - } - - @Override - public void visitMethodInsn( - final int opcode, - final String owner, - final String name, - final String descriptor, - final boolean isInterface) { - super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); - methodRefs.add(new MethodRef(owner, name, descriptor)); + return super.visitMethod(access, name, descriptor, signature, exceptions); } @Override public void visitEnd() { - /* boolean isBridge = hasBridgeFlag; - - if (!isBridge && methodRefs.size() == 1) { - System.out.println("Found suspicious bridge-looking method: " + classEntry.getFullyQualifiedName() + ":" + entry); + JarClassEntry entry = jar.getClass(populator.name, populator); + for (JarFieldEntry field : fields) { + entry.fields.put(field.getKey(), field); + } + for (JarMethodEntry method : methods) { + entry.methods.put(method.getKey(), method); } - if (isBridge) { - for (MethodRef ref : methodRefs) { - JarClassEntry targetClass = jar.getClass(ref.owner, true); - JarMethodEntry targetMethod = new JarMethodEntry(0, ref.name, ref.descriptor, null); - String targetKey = targetMethod.getKey(); - - targetClass.relatedMethods.computeIfAbsent(targetKey, (a) -> new HashSet<>()).add(Pair.of(classEntry, entry.getKey())); - classEntry.relatedMethods.computeIfAbsent(entry.getKey(), (a) -> new HashSet<>()).add(Pair.of(targetClass, targetKey)); - } - } */ - } - } - - private class VisitorMethod extends MethodVisitor { - final JarClassEntry classEntry; - final JarMethodEntry entry; - - public VisitorMethod(int api, MethodVisitor methodVisitor, JarClassEntry classEntry, JarMethodEntry entry) { - super(api, methodVisitor); - this.classEntry = classEntry; - this.entry = entry; + super.visitEnd(); } } @@ -219,9 +158,9 @@ public void apply() throws IOException { System.err.println("Read " + this.jar.getAllClasses().size() + " (" + this.jar.getClasses().size() + ") classes."); - // Stage 2: find subclasses - this.jar.getAllClasses().forEach((c) -> c.populateParents(jar)); - System.err.println("Populated subclass entries."); + // Stage 2: find subclasses and inner classes + this.jar.getAllClasses().forEach((c) -> c.populateRelations(jar)); + System.err.println("Populated subclass and inner class entries."); // Stage 3: join identical MethodEntries if (joinMethodEntries) { diff --git a/src/main/java/net/fabricmc/stitch/representation/JarRootEntry.java b/src/main/java/net/fabricmc/stitch/representation/JarRootEntry.java index 169ecb5..bc5a7cc 100644 --- a/src/main/java/net/fabricmc/stitch/representation/JarRootEntry.java +++ b/src/main/java/net/fabricmc/stitch/representation/JarRootEntry.java @@ -23,49 +23,30 @@ public class JarRootEntry extends AbstractJarEntry implements ClassStorage { final Object syncObject = new Object(); final File file; final Map classTree; - final List allClasses; + final Map allClasses; public JarRootEntry(File file) { super(file.getName()); this.file = file; this.classTree = new TreeMap<>(Comparator.naturalOrder()); - this.allClasses = new ArrayList<>(); + this.allClasses = new TreeMap<>(); } @Override - public JarClassEntry getClass(String name, boolean create) { + public JarClassEntry getClass(String name, JarClassEntry.Populator populator) { if (name == null) { return null; } - String[] nameSplit = name.split("\\$"); - int i = 0; - - JarClassEntry parent; - JarClassEntry entry = classTree.get(nameSplit[i++]); - if (entry == null && create) { - entry = new JarClassEntry(nameSplit[0], nameSplit[0]); + JarClassEntry entry = allClasses.get(name); + if (entry == null && populator != null) { + entry = new JarClassEntry(name); + entry.populate(populator); synchronized (syncObject) { - allClasses.add(entry); - classTree.put(entry.getName(), entry); - } - } - - StringBuilder fullyQualifiedBuilder = new StringBuilder(nameSplit[0]); - - while (i < nameSplit.length && entry != null) { - fullyQualifiedBuilder.append('$'); - fullyQualifiedBuilder.append(nameSplit[i]); - - parent = entry; - entry = entry.getInnerClass(nameSplit[i++]); - - if (entry == null && create) { - entry = new JarClassEntry(nameSplit[i - 1], fullyQualifiedBuilder.toString()); - synchronized (syncObject) { - allClasses.add(entry); - parent.innerClasses.put(entry.getName(), entry); + allClasses.put(name, entry); + if (!entry.hasDeclaringClass() && !entry.hasEnclosingClass()) { + classTree.put(name, entry); } } } @@ -78,6 +59,6 @@ public Collection getClasses() { } public Collection getAllClasses() { - return Collections.unmodifiableList(allClasses); + return allClasses.values(); } }