Skip to content

Commit

Permalink
add bridge detection for finding related methods in intermediary proc…
Browse files Browse the repository at this point in the history
…essing; add asmTrace command as a thin wrapper on TraceClassVisitor
  • Loading branch information
asiekierka committed Dec 15, 2018
1 parent 41ea7d4 commit 2e001d7
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 42 deletions.
1 change: 1 addition & 0 deletions src/main/java/net/fabricmc/stitch/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public static void addCommand(Command command) {
}

static {
addCommand(new CommandAsmTrace());
addCommand(new CommandGenerateIntermediary());
addCommand(new CommandMergeJar());
addCommand(new CommandMergeTiny());
Expand Down
49 changes: 49 additions & 0 deletions src/main/java/net/fabricmc/stitch/commands/CommandAsmTrace.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright (c) 2016, 2017, 2018 Adrian Siekierka
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package net.fabricmc.stitch.commands;

import net.fabricmc.stitch.Command;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.util.Textifier;
import org.objectweb.asm.util.TraceClassVisitor;

import java.io.FileInputStream;
import java.io.PrintWriter;

public class CommandAsmTrace extends Command {
public CommandAsmTrace() {
super("asmTrace");
}

@Override
public String getHelpString() {
return "<class-file>";
}

@Override
public boolean isArgumentCountValid(int count) {
return count == 1;
}

@Override
public void run(String[] args) throws Exception {
ClassReader cr = new ClassReader(new FileInputStream(args[0]));
ClassVisitor tcv = new TraceClassVisitor(new PrintWriter(System.out));
cr.accept(tcv, 0);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,13 @@ public void run(String[] args) throws Exception {
System.err.println("Loading mapping file...");
state.prepareRewrite(new File(args[1]));

File outFile = new File(args[2]);
if (outFile.exists()) {
outFile.delete();
}

System.err.println("Rewriting mappings...");
state.generate(new File(args[2]), jarOld, jarOld);
state.generate(outFile, jarOld, jarOld);
System.err.println("Done!");
}

Expand Down
111 changes: 74 additions & 37 deletions src/main/java/net/fabricmc/stitch/commands/GenState.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import net.fabricmc.stitch.representation.*;
import net.fabricmc.stitch.util.MatcherUtil;
import net.fabricmc.stitch.util.Pair;
import net.fabricmc.stitch.util.StitchUtil;
import net.fabricmc.tinyremapper.TinyUtils;
import org.checkerframework.checker.nullness.qual.Nullable;
Expand Down Expand Up @@ -128,58 +129,90 @@ private String getNamesListEntry(ClassEntry classEntry) {
return builder.toString();
}

@Nullable
private String getMethodName(ClassStorage storageOld, ClassStorage storageNew, ClassEntry c, MethodEntry m) {
if (!isMappedMethod(storageNew, c, m)) {
return null;
private Set<MethodEntry> findNames(ClassStorage storageOld, ClassStorage storageNew, ClassEntry c, MethodEntry m, Map<String, Set<String>> names) {
Set<MethodEntry> allEntries = new HashSet<>();
findNames(storageOld, storageNew, c, m, names, allEntries);
return allEntries;
}

private void findNames(ClassStorage storageOld, ClassStorage storageNew, ClassEntry c, MethodEntry m, Map<String, Set<String>> names, Set<MethodEntry> usedMethods) {
if (!usedMethods.add(m)) {
return;
}

if (methodNames.containsKey(m)) {
return methodNames.get(m);
String suffix = "." + m.getName() + m.getDescriptor();

if ((m.getAccess() & Opcodes.ACC_BRIDGE) != 0) {
suffix += "(bridge)";
}

if (newToOld != null || newToIntermediary != null) {
List<ClassEntry> ccList = m.getMatchingEntries(storageNew, c);
Map<String, Set<String>> names = new HashMap<>();
List<ClassEntry> ccList = m.getMatchingEntries(storageNew, c);

for (ClassEntry cc : ccList) {
GenMap.DescEntry findEntry = null;
if (newToIntermediary != null) {
findEntry = newToIntermediary.getMethod(cc.getFullyQualifiedName(), m.getName(), m.getDescriptor());
if (findEntry != null) {
names.computeIfAbsent(findEntry.getName(), (s) -> new TreeSet<>()).add(getNamesListEntry(cc));
}
for (ClassEntry cc : ccList) {
GenMap.DescEntry findEntry = null;
if (newToIntermediary != null) {
findEntry = newToIntermediary.getMethod(cc.getFullyQualifiedName(), m.getName(), m.getDescriptor());
if (findEntry != null) {
names.computeIfAbsent(findEntry.getName(), (s) -> new TreeSet<>()).add(getNamesListEntry(cc) + suffix);
}
}

if (findEntry == null && newToOld != null) {
findEntry = newToOld.getMethod(cc.getFullyQualifiedName(), m.getName(), m.getDescriptor());
if (findEntry == null && newToOld != null) {
findEntry = newToOld.getMethod(cc.getFullyQualifiedName(), m.getName(), m.getDescriptor());
if (findEntry != null) {
GenMap.DescEntry newToOldEntry = findEntry;
findEntry = oldToIntermediary.getMethod(newToOldEntry);
if (findEntry != null) {
GenMap.DescEntry newToOldEntry = findEntry;
findEntry = oldToIntermediary.getMethod(newToOldEntry);
if (findEntry != null) {
names.computeIfAbsent(findEntry.getName(), (s) -> new TreeSet<>()).add(getNamesListEntry(cc));
} else {
// more involved...
ClassEntry oldBase = storageOld.getClass(newToOldEntry.getOwner(), false);
if (oldBase != null) {
MethodEntry oldM = oldBase.getMethod(newToOldEntry.getName() + newToOldEntry.getDesc());
List<ClassEntry> cccList = oldM.getMatchingEntries(storageOld, oldBase);

for (ClassEntry ccc : cccList) {
findEntry = oldToIntermediary.getMethod(ccc.getFullyQualifiedName(), oldM.getName(), oldM.getDescriptor());
if (findEntry != null) {
names.computeIfAbsent(findEntry.getName(), (s) -> new TreeSet<>()).add(getNamesListEntry(ccc));
}
names.computeIfAbsent(findEntry.getName(), (s) -> new TreeSet<>()).add(getNamesListEntry(cc) + suffix);
} else {
// more involved...
ClassEntry oldBase = storageOld.getClass(newToOldEntry.getOwner(), false);
if (oldBase != null) {
MethodEntry oldM = oldBase.getMethod(newToOldEntry.getName() + newToOldEntry.getDesc());
List<ClassEntry> cccList = oldM.getMatchingEntries(storageOld, oldBase);

for (ClassEntry ccc : cccList) {
findEntry = oldToIntermediary.getMethod(ccc.getFullyQualifiedName(), oldM.getName(), oldM.getDescriptor());
if (findEntry != null) {
names.computeIfAbsent(findEntry.getName(), (s) -> new TreeSet<>()).add(getNamesListEntry(ccc) + suffix);
}
}
}
}
}
}
}

for (ClassEntry mc : ccList) {
for (Pair<ClassEntry, String> pair : mc.getRelatedMethods(m)) {
findNames(storageOld, storageNew, pair.getLeft(), pair.getLeft().getMethod(pair.getRight()), names, usedMethods);
}
}
}

@Nullable
private String getMethodName(ClassStorage storageOld, ClassStorage storageNew, ClassEntry c, MethodEntry m) {
if (!isMappedMethod(storageNew, c, m)) {
return null;
}

if (methodNames.containsKey(m)) {
return methodNames.get(m);
}

if (newToOld != null || newToIntermediary != null) {
Map<String, Set<String>> names = new HashMap<>();
Set<MethodEntry> allEntries = findNames(storageOld, storageNew, c, m, names);
for (MethodEntry mm : allEntries) {
if (methodNames.containsKey(mm)) {
return methodNames.get(mm);
}
}

if (names.size() > 1) {
System.out.println("Conflict detected - matched same target name!");
List<String> nameList = new ArrayList<>(names.keySet());
Collections.sort(nameList);

for (int i = 0; i < nameList.size(); i++) {
String s = nameList.get(i);
Expand All @@ -201,14 +234,18 @@ private String getMethodName(ClassStorage storageOld, ClassStorage storageNew, C
}

if (i >= 1 && i <= nameList.size()) {
methodNames.put(m, nameList.get(i - 1));
for (MethodEntry mm : allEntries) {
methodNames.put(mm, nameList.get(i - 1));
}
System.out.println("OK!");
break;
return nameList.get(i - 1);
}
}
} else if (names.size() == 1) {
String s = names.keySet().iterator().next();
methodNames.put(m, s);
for (MethodEntry mm : allEntries) {
methodNames.put(mm, s);
}
return s;
}
}
Expand Down
19 changes: 19 additions & 0 deletions src/main/java/net/fabricmc/stitch/representation/ClassEntry.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package net.fabricmc.stitch.representation;

import net.fabricmc.stitch.util.Pair;
import org.objectweb.asm.commons.Remapper;

import java.util.*;
Expand All @@ -26,6 +27,7 @@ public class ClassEntry extends Entry {
final Map<String, ClassEntry> innerClasses;
final Map<String, FieldEntry> fields;
final Map<String, MethodEntry> methods;
final Map<String, Set<Pair<ClassEntry, String>>> relatedMethods;

String signature;
String superclass;
Expand All @@ -40,6 +42,7 @@ protected ClassEntry(String name, String fullyQualifiedName) {
this.innerClasses = new TreeMap<>(Comparator.naturalOrder());
this.fields = new TreeMap<>(Comparator.naturalOrder());
this.methods = new TreeMap<>(Comparator.naturalOrder());
this.relatedMethods = new HashMap<>();

this.subclasses = new ArrayList<>();
this.implementers = new ArrayList<>();
Expand All @@ -65,6 +68,12 @@ protected void populateParents(ClassStorage storage) {
}
}

// unstable
public Collection<Pair<ClassEntry, String>> getRelatedMethods(MethodEntry m) {
//noinspection unchecked
return relatedMethods.getOrDefault(m.getKey(), Collections.EMPTY_SET);
}

public String getFullyQualifiedName() {
return fullyQualifiedName;
}
Expand Down Expand Up @@ -170,6 +179,7 @@ public void remap(Remapper remapper) {
Map<String, ClassEntry> innerClassOld = new HashMap<>(innerClasses);
Map<String, FieldEntry> fieldsOld = new HashMap<>(fields);
Map<String, MethodEntry> methodsOld = new HashMap<>(methods);
Map<String, String> methodKeyRemaps = new HashMap<>();

innerClasses.clear();
fields.clear();
Expand All @@ -188,6 +198,15 @@ public void remap(Remapper remapper) {
for (Map.Entry<String, MethodEntry> entry : methodsOld.entrySet()) {
entry.getValue().remap(this, oldName, remapper);
methods.put(entry.getValue().getKey(), entry.getValue());
methodKeyRemaps.put(entry.getKey(), entry.getValue().getKey());
}

// TODO: remap relatedMethods strings???
Map<String, Set<Pair<ClassEntry, String>>> relatedMethodsOld = new HashMap<>(relatedMethods);
relatedMethods.clear();

for (Map.Entry<String, Set<Pair<ClassEntry, String>>> entry : relatedMethodsOld.entrySet()) {
relatedMethods.put(methodKeyRemaps.getOrDefault(entry.getKey(), entry.getKey()), entry.getValue());
}
}
}
36 changes: 32 additions & 4 deletions src/main/java/net/fabricmc/stitch/representation/JarReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,13 @@ public MethodVisitor visitMethod(final int access, final String name, final Stri
MethodEntry method = new MethodEntry(access, name, descriptor, signature);
this.entry.methods.put(method.getKey(), method);

return new VisitorMethod(api, super.visitMethod(access, name, descriptor, signature, exceptions),
entry, method);
if ((access & Opcodes.ACC_BRIDGE) != 0) {
return new VisitorBridge(api, super.visitMethod(access, name, descriptor, signature, exceptions),
entry, method);
} else {
return new VisitorMethod(api, super.visitMethod(access, name, descriptor, signature, exceptions),
entry, method);
}
}
}

Expand All @@ -112,9 +117,32 @@ public VisitorField(int api, FieldVisitor fieldVisitor, ClassEntry classEntry, F
}
}

private class VisitorBridge extends VisitorMethod {
public VisitorBridge(int api, MethodVisitor methodVisitor, ClassEntry classEntry, MethodEntry entry) {
super(api, methodVisitor, classEntry, entry);
}

@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);

ClassEntry targetClass = jar.getClass(owner, true);
MethodEntry targetMethod = new MethodEntry(0, name, 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 {
private final ClassEntry classEntry;
private final MethodEntry entry;
final ClassEntry classEntry;
final MethodEntry entry;

public VisitorMethod(int api, MethodVisitor methodVisitor, ClassEntry classEntry, MethodEntry entry) {
super(api, methodVisitor);
Expand Down

0 comments on commit 2e001d7

Please sign in to comment.