diff options
Diffstat (limited to 'src/main/java/cuchaz/enigma/analysis')
| -rw-r--r-- | src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java | 150 |
1 files changed, 150 insertions, 0 deletions
diff --git a/src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java b/src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java new file mode 100644 index 0000000..12ef709 --- /dev/null +++ b/src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java | |||
| @@ -0,0 +1,150 @@ | |||
| 1 | package cuchaz.enigma.analysis; | ||
| 2 | |||
| 3 | import com.strobel.core.Pair; | ||
| 4 | import cuchaz.enigma.api.EnigmaPlugin; | ||
| 5 | import cuchaz.enigma.api.EnigmaPluginContext; | ||
| 6 | import cuchaz.enigma.api.service.JarIndexerService; | ||
| 7 | import cuchaz.enigma.api.service.NameProposalService; | ||
| 8 | import cuchaz.enigma.translation.mapping.ResolutionStrategy; | ||
| 9 | import cuchaz.enigma.translation.representation.TypeDescriptor; | ||
| 10 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 11 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 12 | import cuchaz.enigma.translation.representation.entry.FieldEntry; | ||
| 13 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | ||
| 14 | import org.objectweb.asm.ClassReader; | ||
| 15 | import org.objectweb.asm.ClassVisitor; | ||
| 16 | import org.objectweb.asm.FieldVisitor; | ||
| 17 | import org.objectweb.asm.MethodVisitor; | ||
| 18 | import org.objectweb.asm.Opcodes; | ||
| 19 | import org.objectweb.asm.tree.AbstractInsnNode; | ||
| 20 | import org.objectweb.asm.tree.FieldInsnNode; | ||
| 21 | import org.objectweb.asm.tree.InsnList; | ||
| 22 | import org.objectweb.asm.tree.LdcInsnNode; | ||
| 23 | import org.objectweb.asm.tree.MethodInsnNode; | ||
| 24 | import org.objectweb.asm.tree.MethodNode; | ||
| 25 | import org.objectweb.asm.tree.analysis.Analyzer; | ||
| 26 | import org.objectweb.asm.tree.analysis.Frame; | ||
| 27 | import org.objectweb.asm.tree.analysis.SourceInterpreter; | ||
| 28 | import org.objectweb.asm.tree.analysis.SourceValue; | ||
| 29 | |||
| 30 | import java.util.ArrayList; | ||
| 31 | import java.util.HashMap; | ||
| 32 | import java.util.HashSet; | ||
| 33 | import java.util.List; | ||
| 34 | import java.util.Map; | ||
| 35 | import java.util.Optional; | ||
| 36 | import java.util.Set; | ||
| 37 | import java.util.function.UnaryOperator; | ||
| 38 | |||
| 39 | public final class BuiltinPlugin implements EnigmaPlugin { | ||
| 40 | |||
| 41 | public BuiltinPlugin() { | ||
| 42 | } | ||
| 43 | |||
| 44 | @Override | ||
| 45 | public void init(EnigmaPluginContext ctx) { | ||
| 46 | registerEnumNamingService(ctx); | ||
| 47 | } | ||
| 48 | |||
| 49 | private void registerEnumNamingService(EnigmaPluginContext ctx) { | ||
| 50 | final Map<Entry<?>, String> names = new HashMap<>(); | ||
| 51 | final EnumFieldNameFindingVisitor visitor = new EnumFieldNameFindingVisitor(names); | ||
| 52 | |||
| 53 | ctx.registerService("enigma:enum_initializer_indexer", JarIndexerService.TYPE, ctx1 -> (classCache, jarIndex) -> classCache.visit(() -> visitor, ClassReader.SKIP_FRAMES)); | ||
| 54 | ctx.registerService("enigma:enum_name_proposer", NameProposalService.TYPE, ctx1 -> (obfEntry, remapper) -> Optional.ofNullable(names.get(obfEntry))); | ||
| 55 | } | ||
| 56 | |||
| 57 | private static final class EnumFieldNameFindingVisitor extends ClassVisitor { | ||
| 58 | |||
| 59 | private ClassEntry clazz; | ||
| 60 | private String className; | ||
| 61 | private final Map<Entry<?>, String> mappings; | ||
| 62 | private final Set<Pair<String, String>> enumFields = new HashSet<>(); | ||
| 63 | private final List<MethodNode> classInits = new ArrayList<>(); | ||
| 64 | |||
| 65 | EnumFieldNameFindingVisitor(Map<Entry<?>, String> mappings) { | ||
| 66 | super(Opcodes.ASM7); | ||
| 67 | this.mappings = mappings; | ||
| 68 | } | ||
| 69 | |||
| 70 | @Override | ||
| 71 | public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { | ||
| 72 | super.visit(version, access, name, signature, superName, interfaces); | ||
| 73 | this.className = name; | ||
| 74 | this.clazz = new ClassEntry(name); | ||
| 75 | this.enumFields.clear(); | ||
| 76 | this.classInits.clear(); | ||
| 77 | } | ||
| 78 | |||
| 79 | @Override | ||
| 80 | public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { | ||
| 81 | if ((access & Opcodes.ACC_ENUM) != 0) { | ||
| 82 | if (!enumFields.add(new Pair<>(name, descriptor))) { | ||
| 83 | throw new IllegalArgumentException("Found two enum fields with the same name \"" + name + "\" and desc \"" + descriptor + "\"!"); | ||
| 84 | } | ||
| 85 | } | ||
| 86 | return super.visitField(access, name, descriptor, signature, value); | ||
| 87 | } | ||
| 88 | |||
| 89 | @Override | ||
| 90 | public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { | ||
| 91 | if ("<clinit>".equals(name)) { | ||
| 92 | MethodNode node = new MethodNode(api, access, name, descriptor, signature, exceptions); | ||
| 93 | classInits.add(node); | ||
| 94 | return node; | ||
| 95 | } | ||
| 96 | return super.visitMethod(access, name, descriptor, signature, exceptions); | ||
| 97 | } | ||
| 98 | |||
| 99 | @Override | ||
| 100 | public void visitEnd() { | ||
| 101 | super.visitEnd(); | ||
| 102 | try { | ||
| 103 | collectResults(); | ||
| 104 | } catch (Exception ex) { | ||
| 105 | throw new RuntimeException(ex); | ||
| 106 | } | ||
| 107 | } | ||
| 108 | |||
| 109 | private void collectResults() throws Exception { | ||
| 110 | String owner = className; | ||
| 111 | Analyzer<SourceValue> analyzer = new Analyzer<>(new SourceInterpreter()); | ||
| 112 | |||
| 113 | for (MethodNode mn : classInits) { | ||
| 114 | Frame<SourceValue>[] frames = analyzer.analyze(className, mn); | ||
| 115 | |||
| 116 | InsnList instrs = mn.instructions; | ||
| 117 | for (int i = 1; i < instrs.size(); i++) { | ||
| 118 | AbstractInsnNode instr1 = instrs.get(i - 1); | ||
| 119 | AbstractInsnNode instr2 = instrs.get(i); | ||
| 120 | String s = null; | ||
| 121 | |||
| 122 | if (instr2.getOpcode() == Opcodes.PUTSTATIC | ||
| 123 | && ((FieldInsnNode) instr2).owner.equals(owner) | ||
| 124 | && enumFields.contains(new Pair<>(((FieldInsnNode) instr2).name, ((FieldInsnNode) instr2).desc)) | ||
| 125 | && instr1.getOpcode() == Opcodes.INVOKESPECIAL | ||
| 126 | && "<init>".equals(((MethodInsnNode) instr1).name)) { | ||
| 127 | |||
| 128 | for (int j = 0; j < frames[i - 1].getStackSize(); j++) { | ||
| 129 | SourceValue sv = frames[i - 1].getStack(j); | ||
| 130 | for (AbstractInsnNode ci : sv.insns) { | ||
| 131 | if (ci instanceof LdcInsnNode && ((LdcInsnNode) ci).cst instanceof String) { | ||
| 132 | //if (s == null || !s.equals(((LdcInsnNode) ci).cst)) { | ||
| 133 | if (s == null) { | ||
| 134 | s = (String) (((LdcInsnNode) ci).cst); | ||
| 135 | // stringsFound++; | ||
| 136 | } | ||
| 137 | } | ||
| 138 | } | ||
| 139 | } | ||
| 140 | } | ||
| 141 | |||
| 142 | if (s != null) { | ||
| 143 | mappings.put(new FieldEntry(clazz, ((FieldInsnNode) instr2).name, new TypeDescriptor(((FieldInsnNode) instr2).desc)), s); | ||
| 144 | } | ||
| 145 | // report otherwise? | ||
| 146 | } | ||
| 147 | } | ||
| 148 | } | ||
| 149 | } | ||
| 150 | } | ||