Skip to content
This repository has been archived by the owner on Jan 23, 2025. It is now read-only.

Commit

Permalink
Change enum field automapping to use enum field detection instead (#11)
Browse files Browse the repository at this point in the history
Now infers anonymous enclosed enum subclasses

Signed-off-by: liach <liach@users.noreply.github.com>
  • Loading branch information
liach authored and asiekierka committed Jun 29, 2019
1 parent 36a7433 commit c5f6f37
Show file tree
Hide file tree
Showing 3 changed files with 232 additions and 193 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import cuchaz.enigma.translation.representation.entry.FieldEntry;
import net.fabricmc.mappings.EntryTriple;
import net.fabricmc.stitch.util.FieldNameFinder;
import net.fabricmc.stitch.util.NameFinderVisitor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
Expand All @@ -36,6 +37,7 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;

public class StitchNameProposalService {
Expand All @@ -46,39 +48,13 @@ private StitchNameProposalService(EnigmaPluginContext ctx) {
@Override
public void acceptJar(ClassCache classCache, JarIndex jarIndex) {

Map<String, Set<String>> enumFields = new HashMap<>();
Map<String, List<MethodNode>> methods = new HashMap<>();

classCache.visit(new Supplier<ClassVisitor>() {
@Override
public ClassVisitor get() {
return new ClassVisitor(Opcodes.ASM7) {

String owner;

@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
this.owner = name;
super.visit(version, access, name, signature, superName, interfaces);
}

@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
if ("<clinit>".equals(name)) {
MethodNode node = new MethodNode(api, access, name, descriptor, signature, exceptions);

methods.computeIfAbsent(owner, s -> new ArrayList<>()).add(node);

return node;
} else {
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
}
};
}
}, ClassReader.SKIP_FRAMES);
classCache.visit(() -> new NameFinderVisitor(Opcodes.ASM7, enumFields, methods), ClassReader.SKIP_FRAMES);

try {
fieldNames = new FieldNameFinder().findNames(methods);
fieldNames = new FieldNameFinder().findNames(enumFields, methods);
} catch (Exception e) {
throw new RuntimeException(e);
}
Expand Down
318 changes: 154 additions & 164 deletions src/main/java/net/fabricmc/stitch/util/FieldNameFinder.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,184 +17,174 @@
package net.fabricmc.stitch.util;

import net.fabricmc.mappings.EntryTriple;
import net.fabricmc.stitch.commands.CommandProposeFieldNames;
import net.fabricmc.stitch.merge.JarMerger;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.*;
import org.objectweb.asm.tree.analysis.*;

import java.io.*;
import java.util.*;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.analysis.Analyzer;
import org.objectweb.asm.tree.analysis.Frame;
import org.objectweb.asm.tree.analysis.SourceInterpreter;
import org.objectweb.asm.tree.analysis.SourceValue;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

public class FieldNameFinder {
private class VClass extends ClassVisitor {
private final List<MethodNode> nodes = new ArrayList<>();

public VClass(int api) {
super(api);
}

@Override
public MethodVisitor visitMethod(
final int access,
final String name,
final String descriptor,
final String signature,
final String[] exceptions) {
if ("<clinit>".equals(name)) {
MethodNode node = new MethodNode(api, access, name, descriptor, signature, exceptions);
nodes.add(node);
return node;
} else {
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
}
}

public Map<EntryTriple, String> findNames(Iterable<byte[]> classes) throws Exception {
Map<String, List<MethodNode>> methods = new HashMap<>();
Map<String, List<MethodNode>> methods = new HashMap<>();
Map<String, Set<String>> enumFields = new HashMap<>();

for (byte[] data : classes) {
ClassReader reader = new ClassReader(data);
String owner = reader.getClassName();
VClass vClass = new VClass(Opcodes.ASM7);
NameFinderVisitor vClass = new NameFinderVisitor(Opcodes.ASM7, enumFields, methods);
reader.accept(vClass, ClassReader.SKIP_FRAMES);
methods.put(owner, vClass.nodes);
}

return findNames(methods);
return findNames(enumFields, methods);
}

public Map<EntryTriple, String> findNames(Map<String, List<MethodNode>> classes) throws Exception {
Analyzer<SourceValue> analyzer = new Analyzer<>(new SourceInterpreter());
Map<EntryTriple, String> fieldNames = new HashMap<>();
Map<String, Set<String>> fieldNamesUsed = new HashMap<>();
Map<String, Set<String>> fieldNamesDuplicate = new HashMap<>();

for (Map.Entry<String, List<MethodNode>> entry : classes.entrySet()) {
String owner = entry.getKey();
for (MethodNode mn : entry.getValue()) {
Frame<SourceValue>[] frames = analyzer.analyze(owner, mn);

InsnList instrs = mn.instructions;
for (int i = 1; i < instrs.size(); i++) {
AbstractInsnNode instr1 = instrs.get(i - 1);
AbstractInsnNode instr2 = instrs.get(i);
String s = null;

if (instr2.getOpcode() == Opcodes.PUTSTATIC && ((FieldInsnNode) instr2).owner.equals(owner)
&& instr1 instanceof MethodInsnNode && ((MethodInsnNode) instr1).owner.equals(owner)
&& (instr1.getOpcode() == Opcodes.INVOKESTATIC || (instr1.getOpcode() == Opcodes.INVOKESPECIAL && "<init>".equals(((MethodInsnNode) instr1).name)))) {

for (int j = 0; j < frames[i - 1].getStackSize(); j++) {
SourceValue sv = frames[i - 1].getStack(j);
for (AbstractInsnNode ci : sv.insns) {
if (ci instanceof LdcInsnNode && ((LdcInsnNode) ci).cst instanceof String) {
//if (s == null || !s.equals(((LdcInsnNode) ci).cst)) {
if (s == null) {
s = (String) (((LdcInsnNode) ci).cst);
// stringsFound++;
}
}
}
}
}

if (s != null) {
if (s.contains(":")) {
s = s.substring(s.indexOf(':') + 1);
}

if (s.contains("/")) {
int separator = s.indexOf('/');
String sFirst = s.substring(0, separator);
String sLast;
if (s.contains(".") && s.indexOf('.') > separator) {
sLast = s.substring(separator + 1, s.indexOf('.'));
} else {
sLast = s.substring(separator + 1);
}
if (sFirst.endsWith("s")) {
sFirst = sFirst.substring(0, sFirst.length() - 1);
}
s = sLast + "_" + sFirst;
}

String oldS = s;
boolean hasAlpha = false;

for (int j = 0; j < s.length(); j++) {
char c = s.charAt(j);

if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) {
hasAlpha = true;
}

if (!(c >= 'A' && c <= 'Z') && !(c >= 'a' && c <= 'z') && !(c >= '0' && c <= '9') && !(c == '_')) {
s = s.substring(0, j) + "_" + s.substring(j + 1);
} else if (j > 0 && Character.isUpperCase(s.charAt(j)) && Character.isLowerCase(s.charAt(j - 1))) {
s = s.substring(0, j) + "_" + s.substring(j, j + 1).toLowerCase(Locale.ROOT) + s.substring(j + 1);
}
}

if (hasAlpha) {
s = s.toUpperCase(Locale.ROOT);

Set<String> usedNames = fieldNamesUsed.computeIfAbsent(((FieldInsnNode) instr2).owner, (a) -> new HashSet<>());
Set<String> usedNamesDuplicate = fieldNamesDuplicate.computeIfAbsent(((FieldInsnNode) instr2).owner, (a) -> new HashSet<>());

if (!usedNamesDuplicate.contains(s)) {
if (!usedNames.add(s)) {
System.err.println("Warning: Duplicate key: " + s + " (" + oldS + ")!");
usedNamesDuplicate.add(s);
usedNames.remove(s);
}
}

if (usedNames.contains(s)) {
fieldNames.put(new EntryTriple(((FieldInsnNode) instr2).owner, ((FieldInsnNode) instr2).name, ((FieldInsnNode) instr2).desc), s);
}
}
}
}
}
}

return fieldNames;
}

public Map<EntryTriple, String> findNames(File file) {
List<byte[]> byteArrays = new ArrayList<>();

try {
try (FileInputStream fis = new FileInputStream(file);
JarInputStream jis = new JarInputStream(fis)) {
byte[] buffer = new byte[32768];
JarEntry entry;

while ((entry = jis.getNextJarEntry()) != null) {
if (!entry.getName().endsWith(".class")) {
continue;
}

ByteArrayOutputStream stream = new ByteArrayOutputStream();
int l;
while ((l = jis.read(buffer, 0, buffer.length)) > 0) {
stream.write(buffer, 0, l);
}

byteArrays.add(stream.toByteArray());
}
}

return findNames(byteArrays);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public Map<EntryTriple, String> findNames(Map<String, Set<String>> allEnumFields, Map<String, List<MethodNode>> classes) throws Exception {
Analyzer<SourceValue> analyzer = new Analyzer<>(new SourceInterpreter());
Map<EntryTriple, String> fieldNames = new HashMap<>();
Map<String, Set<String>> fieldNamesUsed = new HashMap<>();
Map<String, Set<String>> fieldNamesDuplicate = new HashMap<>();

for (Map.Entry<String, List<MethodNode>> entry : classes.entrySet()) {
String owner = entry.getKey();
Set<String> enumFields = allEnumFields.getOrDefault(owner, Collections.emptySet());
for (MethodNode mn : entry.getValue()) {
Frame<SourceValue>[] frames = analyzer.analyze(owner, mn);

InsnList instrs = mn.instructions;
for (int i = 1; i < instrs.size(); i++) {
AbstractInsnNode instr1 = instrs.get(i - 1);
AbstractInsnNode instr2 = instrs.get(i);
String s = null;

if (instr2.getOpcode() == Opcodes.PUTSTATIC && ((FieldInsnNode) instr2).owner.equals(owner)
&& (instr1 instanceof MethodInsnNode && ((MethodInsnNode) instr1).owner.equals(owner) || enumFields.contains(((FieldInsnNode) instr2).desc + ((FieldInsnNode) instr2).name))
&& (instr1.getOpcode() == Opcodes.INVOKESTATIC || (instr1.getOpcode() == Opcodes.INVOKESPECIAL && "<init>".equals(((MethodInsnNode) instr1).name)))) {

for (int j = 0; j < frames[i - 1].getStackSize(); j++) {
SourceValue sv = frames[i - 1].getStack(j);
for (AbstractInsnNode ci : sv.insns) {
if (ci instanceof LdcInsnNode && ((LdcInsnNode) ci).cst instanceof String) {
//if (s == null || !s.equals(((LdcInsnNode) ci).cst)) {
if (s == null) {
s = (String) (((LdcInsnNode) ci).cst);
// stringsFound++;
}
}
}
}
}

if (s != null) {
if (s.contains(":")) {
s = s.substring(s.indexOf(':') + 1);
}

if (s.contains("/")) {
int separator = s.indexOf('/');
String sFirst = s.substring(0, separator);
String sLast;
if (s.contains(".") && s.indexOf('.') > separator) {
sLast = s.substring(separator + 1, s.indexOf('.'));
} else {
sLast = s.substring(separator + 1);
}
if (sFirst.endsWith("s")) {
sFirst = sFirst.substring(0, sFirst.length() - 1);
}
s = sLast + "_" + sFirst;
}

String oldS = s;
boolean hasAlpha = false;

for (int j = 0; j < s.length(); j++) {
char c = s.charAt(j);

if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) {
hasAlpha = true;
}

if (!(c >= 'A' && c <= 'Z') && !(c >= 'a' && c <= 'z') && !(c >= '0' && c <= '9') && !(c == '_')) {
s = s.substring(0, j) + "_" + s.substring(j + 1);
} else if (j > 0 && Character.isUpperCase(s.charAt(j)) && Character.isLowerCase(s.charAt(j - 1))) {
s = s.substring(0, j) + "_" + s.substring(j, j + 1).toLowerCase(Locale.ROOT) + s.substring(j + 1);
}
}

if (hasAlpha) {
s = s.toUpperCase(Locale.ROOT);

Set<String> usedNames = fieldNamesUsed.computeIfAbsent(((FieldInsnNode) instr2).owner, (a) -> new HashSet<>());
Set<String> usedNamesDuplicate = fieldNamesDuplicate.computeIfAbsent(((FieldInsnNode) instr2).owner, (a) -> new HashSet<>());

if (!usedNamesDuplicate.contains(s)) {
if (!usedNames.add(s)) {
System.err.println("Warning: Duplicate key: " + s + " (" + oldS + ")!");
usedNamesDuplicate.add(s);
usedNames.remove(s);
}
}

if (usedNames.contains(s)) {
fieldNames.put(new EntryTriple(((FieldInsnNode) instr2).owner, ((FieldInsnNode) instr2).name, ((FieldInsnNode) instr2).desc), s);
}
}
}
}
}
}

return fieldNames;
}

public Map<EntryTriple, String> findNames(File file) {
List<byte[]> byteArrays = new ArrayList<>();

try {
try (FileInputStream fis = new FileInputStream(file);
JarInputStream jis = new JarInputStream(fis)) {
byte[] buffer = new byte[32768];
JarEntry entry;

while ((entry = jis.getNextJarEntry()) != null) {
if (!entry.getName().endsWith(".class")) {
continue;
}

ByteArrayOutputStream stream = new ByteArrayOutputStream();
int l;
while ((l = jis.read(buffer, 0, buffer.length)) > 0) {
stream.write(buffer, 0, l);
}

byteArrays.add(stream.toByteArray());
}
}

return findNames(byteArrays);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
Loading

0 comments on commit c5f6f37

Please sign in to comment.