Migrate name proposal code to filament

This commit is contained in:
modmuss50 2024-06-20 14:32:05 +01:00
parent 70d538edef
commit 24a83e4ae9
14 changed files with 873 additions and 6 deletions

View File

@ -9,7 +9,6 @@ buildscript {
}
dependencies {
classpath "cuchaz:enigma-cli:${project.enigma_version}"
classpath "net.fabricmc:name-proposal:${project.name_proposal_version}"
}
}
@ -71,7 +70,6 @@ def unpickMetaFile = file("unpick-definitions/unpick.json")
dependencies {
enigmaRuntime "cuchaz:enigma-swing:${project.enigma_version}"
enigmaRuntime "net.fabricmc:name-proposal:${project.name_proposal_version}"
enigmaRuntime "net.fabricmc:cfr:${project.cfr_version}"
javadocClasspath "net.fabricmc:fabric-loader:${project.fabric_loader_version}"
javadocClasspath "org.jetbrains:annotations:${project.jetbrains_annotations_version}"
@ -106,8 +104,8 @@ import net.fabricmc.filament.task.mappingio.CompleteMappingsTask
import net.fabricmc.filament.task.mappingio.ConvertMappingsTask
import net.fabricmc.filament.task.mappingio.FormatMappingsTask
import net.fabricmc.filament.task.mappingio.MergeMappingsTask
import net.fabricmc.filament.nameproposal.MappingNameCompleter
import net.fabricmc.mappingio.format.MappingFormat
import net.fabricmc.nameproposal.MappingNameCompleter
import org.gradle.work.DisableCachingByDefault
import com.diffplug.spotless.LineEnding
import groovy.xml.XmlSlurper
@ -624,7 +622,13 @@ abstract class EnigmaTask extends JavaExec {
abstract Property<File> getMappings()
EnigmaTask() {
classpath = project.configurations.enigmaRuntime
def filamentCodeSource = MappingPoetTask.class.getProtectionDomain().getCodeSource()
def filamentJarFile = new File(filamentCodeSource.getLocation().getFile())
def runtimeClasspath = project.files()
runtimeClasspath.from(project.configurations.enigmaRuntime)
runtimeClasspath.from(filamentJarFile)
classpath = runtimeClasspath
mainClass.set('cuchaz.enigma.gui.Main')
jvmArgs "-Xmx2048m"
}

View File

@ -1 +1 @@
filament_version=0.8.0
filament_version=0.9.0

View File

@ -0,0 +1,23 @@
/*
* Copyright (c) 2021 FabricMC
*
* 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.filament.nameproposal;
import org.objectweb.asm.Opcodes;
public class Constants {
public static int ASM_VERSION = Opcodes.ASM9;
}

View File

@ -0,0 +1,164 @@
/*
* Copyright (c) 2016, 2021 FabricMC
*
* 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.filament.nameproposal;
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 org.objectweb.asm.ClassReader;
import org.objectweb.asm.Opcodes;
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.AnalyzerException;
import org.objectweb.asm.tree.analysis.Frame;
import org.objectweb.asm.tree.analysis.SourceInterpreter;
import org.objectweb.asm.tree.analysis.SourceValue;
public class FieldNameFinder {
public Map<MappingEntry, String> findNames(Iterable<byte[]> classes) throws Exception {
Map<String, List<MethodNode>> methods = new HashMap<>();
Map<String, Set<String>> enumFields = new HashMap<>();
for (byte[] data : classes) {
ClassReader reader = new ClassReader(data);
NameFinderVisitor vClass = new NameFinderVisitor(Constants.ASM_VERSION, enumFields, methods);
reader.accept(vClass, ClassReader.SKIP_FRAMES);
}
return findNames(enumFields, methods);
}
public Map<MappingEntry, String> findNames(Map<String, Set<String>> allEnumFields, Map<String, List<MethodNode>> classes) {
Analyzer<SourceValue> analyzer = new Analyzer<>(new SourceInterpreter());
Map<MappingEntry, 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;
try {
frames = analyzer.analyze(owner, mn);
} catch (AnalyzerException e) {
throw new RuntimeException(e);
}
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.out.println("Warning: Duplicate key: " + s + " (" + oldS + ")!");
usedNamesDuplicate.add(s);
usedNames.remove(s);
}
}
if (usedNames.contains(s)) {
fieldNames.put(new MappingEntry(((FieldInsnNode) instr2).owner, ((FieldInsnNode) instr2).name, ((FieldInsnNode) instr2).desc), s);
}
}
}
}
}
}
return fieldNames;
}
}

View File

@ -0,0 +1,20 @@
/*
* Copyright (c) 2021 FabricMC
*
* 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.filament.nameproposal;
public record MappingEntry(String owner, String name, String desc) {
}

View File

@ -0,0 +1,166 @@
/*
* Copyright (c) 2021 FabricMC
*
* 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.filament.nameproposal;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.regex.Pattern;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.tree.ClassNode;
import net.fabricmc.mappingio.MappedElementKind;
import net.fabricmc.mappingio.MappingReader;
import net.fabricmc.mappingio.MappingWriter;
import net.fabricmc.mappingio.format.MappingFormat;
import net.fabricmc.mappingio.tree.MappingTree;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
public class MappingNameCompleter {
// <intermediaryJar> <inputYarnMappings> <inputIntermediaryMappings> <outputYarnMappings>
public static void main(String[] args) throws IOException {
completeNames(Paths.get(args[0]), Paths.get(args[1]), Paths.get(args[2]), Paths.get(args[3]));
}
public static void completeNames(Path intermediaryJar, Path inputYarnMappings, Path inputIntermediaryMappings, Path outputYarnMappings) throws IOException {
NameFinder nameFinder = new NameFinder();
acceptJar(nameFinder, intermediaryJar);
// We need the full intermediary mappings on their own to lookup the record component's root fields/methods.
nameFinder.acceptIntermediaryMappings(readMappings(inputIntermediaryMappings));
Map<MappingEntry, String> fieldNames = nameFinder.getFieldNames();
Map<MappingEntry, String> methodNames = nameFinder.getMethodNames();
Map<String, String> recordNames = nameFinder.getRecordNames();
System.out.printf("Found %d field names%n", fieldNames.size());
System.out.printf("Found %d method names%n", methodNames.size());
System.out.printf("Found %d record names%n", recordNames.size());
final MemoryMappingTree yarn = readMappings(inputYarnMappings);
final int yarnIntermediaryNs = yarn.getNamespaceId("intermediary");
final int yarnNamedNs = yarn.getNamespaceId("named");
for (Map.Entry<MappingEntry, String> entry : fieldNames.entrySet()) {
MappingEntry mappingEntry = entry.getKey();
// Ensure there is a class mapping for this
yarn.visitClass(mappingEntry.owner());
MemoryMappingTree.ClassMapping classMapping = yarn.getClass(mappingEntry.owner(), yarnIntermediaryNs);
yarn.visitField(mappingEntry.name(), mappingEntry.desc());
MappingTree.FieldMapping fieldMapping = Objects.requireNonNull(classMapping.getField(mappingEntry.name(), mappingEntry.desc(), yarnIntermediaryNs), "Could not find field");
String yarnFieldName = fieldMapping.getName(yarnNamedNs);
if (yarnFieldName == null || yarnFieldName.startsWith("field_") || yarnFieldName.startsWith("comp_")) {
// Set a new dst name if it doesn't have one, or matches intermediary
yarn.visitDstName(MappedElementKind.FIELD, yarnNamedNs, entry.getValue());
}
}
for (Map.Entry<MappingEntry, String> entry : methodNames.entrySet()) {
MappingEntry mappingEntry = entry.getKey();
// Ensure there is a class mapping for this
yarn.visitClass(mappingEntry.owner());
MemoryMappingTree.ClassMapping classMapping = yarn.getClass(mappingEntry.owner(), yarnIntermediaryNs);
yarn.visitMethod(mappingEntry.name(), mappingEntry.desc());
MappingTree.MethodMapping methodMapping = Objects.requireNonNull(classMapping.getMethod(mappingEntry.name(), mappingEntry.desc(), yarnIntermediaryNs), "Could not find method");
String yarnFieldName = methodMapping.getName(yarnNamedNs);
if (yarnFieldName == null || yarnFieldName.startsWith("method_") || yarnFieldName.startsWith("comp_")) {
// Set a new dst name if it doesn't have one, or matches intermediary
yarn.visitDstName(MappedElementKind.METHOD, yarnNamedNs, entry.getValue());
}
}
inheritMappedNamesOfEnclosingClasses(yarn);
try (MappingWriter mappingWriter = MappingWriter.create(outputYarnMappings, MappingFormat.TINY_2_FILE)) {
yarn.accept(mappingWriter);
}
}
private static void acceptJar(NameFinder nameFinder, Path jar) throws IOException {
try (JarInputStream jarInputStream = new JarInputStream(Files.newInputStream(jar))) {
JarEntry entry;
while ((entry = jarInputStream.getNextJarEntry()) != null) {
if (!entry.getName().endsWith(".class")) {
continue;
}
ClassReader reader = new ClassReader(jarInputStream);
ClassNode classNode = new ClassNode();
reader.accept(classNode, 0);
nameFinder.accept(classNode);
}
}
}
private static MemoryMappingTree readMappings(Path path) throws IOException {
MemoryMappingTree mappingTree = new MemoryMappingTree();
MappingReader.read(path, mappingTree);
return mappingTree;
}
/**
* Based off loom: https://github.com/FabricMC/fabric-loom/commit/98d8f3767253a1a3308542c2e896cbf5f4382033.
*/
private static void inheritMappedNamesOfEnclosingClasses(MemoryMappingTree tree) {
int namedIdx = tree.getNamespaceId("named");
// The tree does not have an index by intermediary names by default
tree.setIndexByDstNames(true);
for (MappingTree.ClassMapping classEntry : tree.getClasses()) {
String intermediaryName = Objects.requireNonNull(classEntry.getSrcName());
String namedName = classEntry.getDstName(namedIdx);
// No named name, or intermediary name equals the named - and inner class.
if ((namedName == null || intermediaryName.equals(namedName)) && intermediaryName.contains("$")) {
String[] path = intermediaryName.split(Pattern.quote("$"));
int parts = path.length;
for (int i = parts - 2; i >= 0; i--) {
String currentPath = String.join("$", Arrays.copyOfRange(path, 0, i + 1));
String namedParentClass = tree.mapClassName(currentPath, namedIdx);
if (!namedParentClass.equals(currentPath)) {
classEntry.setDstName(namedParentClass
+ "$" + String.join("$", Arrays.copyOfRange(path, i + 1, path.length)),
namedIdx);
break;
}
}
}
}
}
}

View File

@ -0,0 +1,99 @@
/*
* Copyright (c) 2016, 2021 FabricMC
*
* 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.filament.nameproposal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;
import net.fabricmc.mappingio.tree.MappingTree;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
public class NameFinder {
// comp_x -> name
private final Map<String, String> recordNames = new HashMap<>();
private final Map<MappingEntry, String> recordFieldNames = new HashMap<>();
private final Map<MappingEntry, String> recordMethodNames = new HashMap<>();
private final Map<String, Set<String>> enumFields = new HashMap<>();
private final Map<String, List<MethodNode>> methods = new HashMap<>();
public void accept(ClassNode classNode) {
classNode.accept(new NameFinderVisitor(Constants.ASM_VERSION, enumFields, methods));
if ("java/lang/Record".equals(classNode.superName)) {
classNode.accept(new RecordComponentNameFinder(Constants.ASM_VERSION, recordNames));
}
}
public void acceptIntermediaryMappings(MemoryMappingTree mappingTree) {
if (recordNames.isEmpty()) {
return;
}
final int intermediaryId = mappingTree.getNamespaceId("intermediary");
for (Map.Entry<String, String> entry : recordNames.entrySet()) {
boolean foundMethod = false;
boolean foundField = false;
for (MappingTree.ClassMapping classMapping : mappingTree.getClasses()) {
for (MappingTree.FieldMapping fieldMapping : classMapping.getFields()) {
if (fieldMapping.getName(intermediaryId).equals(entry.getKey())) {
MappingEntry fieldEntry = new MappingEntry(classMapping.getName(intermediaryId), fieldMapping.getName(intermediaryId), fieldMapping.getDesc(intermediaryId));
recordFieldNames.put(fieldEntry, entry.getValue());
foundField = true;
}
}
for (MappingTree.MethodMapping methodMapping : classMapping.getMethods()) {
if (methodMapping.getName(intermediaryId).equals(entry.getKey())) {
MappingEntry fieldEntry = new MappingEntry(classMapping.getName(intermediaryId), methodMapping.getName(intermediaryId), methodMapping.getDesc(intermediaryId));
recordMethodNames.put(fieldEntry, entry.getValue());
foundMethod = true;
}
}
}
if (!foundField) {
System.err.println("Failed to find field for " + entry);
}
if (!foundMethod) {
System.err.println("Failed to find method for " + entry);
}
}
}
public Map<String, String> getRecordNames() {
return recordNames;
}
public Map<MappingEntry, String> getFieldNames() {
Map<MappingEntry, String> fieldNames = new HashMap<>(new FieldNameFinder().findNames(enumFields, methods));
fieldNames.putAll(recordFieldNames);
return fieldNames;
}
public Map<MappingEntry, String> getMethodNames() {
return recordMethodNames;
}
}

View File

@ -0,0 +1,74 @@
/*
* Copyright (c) 2016, 2021 FabricMC
*
* 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.filament.nameproposal;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.MethodNode;
public class NameFinderVisitor extends ClassVisitor {
private String owner;
private final Map<String, Set<String>> allEnumFields;
private final Map<String, List<MethodNode>> allMethods;
public NameFinderVisitor(int api, Map<String, Set<String>> allEnumFields, Map<String, List<MethodNode>> allMethods) {
super(api);
this.allMethods = allMethods;
this.allEnumFields = allEnumFields;
}
@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 FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
if ((access & Opcodes.ACC_ENUM) != 0) {
if (!allEnumFields.computeIfAbsent(owner, s -> new HashSet<>()).add(descriptor + name)) {
throw new IllegalArgumentException("Found two enum fields with the same name \"" + name + "\"!");
}
}
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) {
if ("<clinit>".equals(name)) {
MethodNode node = new MethodNode(api, access, name, descriptor, signature, exceptions);
allMethods.computeIfAbsent(owner, s -> new ArrayList<>()).add(node);
return node;
} else {
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
}
}

View File

@ -0,0 +1,134 @@
/*
* Copyright (c) 2016, 2021 FabricMC
*
* 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.filament.nameproposal;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Handle;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
public final class RecordComponentNameFinder extends ClassVisitor {
static final Handle OBJ_MTH_BOOTSTRAP = new Handle(
Opcodes.H_INVOKESTATIC,
"java/lang/runtime/ObjectMethods",
"bootstrap",
"""
(Ljava/lang/invoke/MethodHandles$Lookup;\
Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;\
Ljava/lang/Class;Ljava/lang/String;\
[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;""",
false
);
static final Set<Component> OBJECT_METHODS = Set.of(
new Component("equals", "(Ljava/lang/Object;)Z"),
new Component("toString", "()Ljava/lang/String;"),
new Component("hashCode", "()I")
);
// comp_x -> name
private final Map<String, String> recordNames;
private String name;
public RecordComponentNameFinder(int api, Map<String, String> recordNames) {
super(api);
this.recordNames = recordNames;
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
super.visit(version, access, name, signature, superName, interfaces);
this.name = name;
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
var nameAndType = new Component(name, descriptor);
if (OBJECT_METHODS.contains(nameAndType)) {
return new ObjectMethodVisitor(api, nameAndType);
}
return null;
}
private class ObjectMethodVisitor extends MethodVisitor {
private final String owner;
private final Component id;
ObjectMethodVisitor(int api, Component id) {
super(api);
this.owner = Objects.requireNonNull(RecordComponentNameFinder.this.name);
this.id = id;
}
@Override
public void visitInvokeDynamicInsn(String name, String descriptor, Handle bootstrapMethodHandle, Object... bootstrapMethodArguments) {
super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments);
if (!bootstrapMethodHandle.equals(OBJ_MTH_BOOTSTRAP) || !id.checkIndy(this.owner, name, descriptor)) {
return;
}
assert bootstrapMethodArguments[0] instanceof Type;
String recordClassName = ((Type) bootstrapMethodArguments[0]).getInternalName();
if (!recordClassName.equals(owner)) {
System.out.println("found mismatching object method bootstrap record class in caller class " + owner + "::" + id);
}
assert bootstrapMethodArguments[1] instanceof String;
String[] names = ((String) bootstrapMethodArguments[1]).split(";");
assert names.length == bootstrapMethodArguments.length - 2;
for (int i = 2; i < bootstrapMethodArguments.length; i++) {
if (bootstrapMethodArguments[i] instanceof Handle handle) {
if (handle.getTag() == Opcodes.H_GETFIELD && handle.getOwner().equals(recordClassName)) {
var argName = names[i - 2];
put(recordNames, handle.getName(), argName);
} else {
// valid bytecode but we cannot guess
System.out.println("found special constant pool method handle, cannot process: " + owner + "::" + id);
}
} else {
// valid bytecode, may be condy bsm arg, can't process
System.out.println("found special bootstrap method arg (maybe condy), cannot process: " + owner + "::" + id);
}
}
}
private void put(Map<String, String> out, String key, String value) {
var old = out.put(key, value);
if (old == null || old.equals(value)) {
return;
}
System.out.println("Found conflicting name for component " + key + " in " + owner + "::"
+ id + ", replaced " + old + " with " + value);
}
}
private record Component(String name, String desc) {
boolean checkIndy(String owner, String name, String desc) {
return this.name.equals(name) && ("(L" + owner + ";" + this.desc.substring(1)).equals(desc);
}
}
}

View File

@ -0,0 +1,72 @@
/*
* Copyright (c) 2021 FabricMC
*
* 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.filament.nameproposal.enigma;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import cuchaz.enigma.analysis.index.JarIndex;
import cuchaz.enigma.api.service.JarIndexerService;
import cuchaz.enigma.api.service.NameProposalService;
import cuchaz.enigma.classprovider.ClassProvider;
import cuchaz.enigma.translation.mapping.EntryRemapper;
import cuchaz.enigma.translation.representation.entry.Entry;
import cuchaz.enigma.translation.representation.entry.FieldEntry;
import cuchaz.enigma.translation.representation.entry.MethodEntry;
import org.objectweb.asm.tree.ClassNode;
import net.fabricmc.filament.nameproposal.MappingEntry;
import net.fabricmc.filament.nameproposal.NameFinder;
public class EnigmaNameProposalService implements JarIndexerService, NameProposalService {
private Map<String, String> recordNames;
Map<MappingEntry, String> fieldNames;
@Override
public void acceptJar(Set<String> classNames, ClassProvider classProvider, JarIndex jarIndex) {
NameFinder nameFinder = new NameFinder();
for (String className : classNames) {
ClassNode classNode = classProvider.get(className);
nameFinder.accept(Objects.requireNonNull(classNode, "Failed to get ClassNode for " + className));
}
recordNames = nameFinder.getRecordNames();
fieldNames = nameFinder.getFieldNames();
}
@Override
public Optional<String> proposeName(Entry<?> obfEntry, EntryRemapper remapper) {
Objects.requireNonNull(recordNames, "Cannot proposeName before indexing");
if (obfEntry instanceof FieldEntry fieldEntry) {
if (fieldEntry.getName().startsWith("comp_")) {
return Optional.ofNullable(recordNames.get(fieldEntry.getName()));
}
return Optional.ofNullable(fieldNames.get(new MappingEntry(fieldEntry.getContainingClass().getFullName(), fieldEntry.getName(), fieldEntry.getDesc().toString())));
} else if (obfEntry instanceof MethodEntry methodEntry) {
if (methodEntry.getName().startsWith("comp_")) {
return Optional.ofNullable(recordNames.get(methodEntry.getName()));
}
}
return Optional.empty();
}
}

View File

@ -0,0 +1,75 @@
/*
* Copyright (c) 2016, 2021 FabricMC
*
* 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.filament.nameproposal.enigma;
import cuchaz.enigma.api.service.EnigmaServiceContext;
import cuchaz.enigma.api.service.ObfuscationTestService;
import cuchaz.enigma.translation.representation.entry.ClassEntry;
import cuchaz.enigma.translation.representation.entry.Entry;
import cuchaz.enigma.translation.representation.entry.FieldEntry;
import cuchaz.enigma.translation.representation.entry.MethodEntry;
public class IntermediaryObfuscationTestService implements ObfuscationTestService {
private final String prefix, classPrefix, classPackagePrefix, fieldPrefix, methodPrefix, componentPrefix;
public IntermediaryObfuscationTestService(EnigmaServiceContext<ObfuscationTestService> context) {
this.prefix = context.getArgument("package").orElse("net/minecraft") + "/";
this.classPrefix = context.getArgument("classPrefix").orElse("class_");
this.fieldPrefix = context.getArgument("fieldPrefix").orElse("field_");
this.methodPrefix = context.getArgument("methodPrefix").orElse("method_");
this.componentPrefix = context.getArgument("componentPrefix").orElse("comp_");
this.classPackagePrefix = this.prefix + this.classPrefix;
}
@Override
public boolean testDeobfuscated(Entry<?> entry) {
if (entry instanceof ClassEntry) {
ClassEntry ce = (ClassEntry) entry;
String[] components = ce.getFullName().split("\\$");
// all obfuscated components are, at their outermost, class_
String lastComponent = components[components.length - 1];
if (lastComponent.startsWith(this.classPrefix) || lastComponent.startsWith(this.classPackagePrefix)) {
return false;
}
} else if (entry instanceof FieldEntry) {
if (entry.getName().startsWith(this.fieldPrefix)) {
return false;
}
if (entry.getName().startsWith(this.componentPrefix)) {
return false;
}
} else if (entry instanceof MethodEntry) {
if (entry.getName().startsWith(this.methodPrefix)) {
return false;
}
if (entry.getName().startsWith(this.componentPrefix)) {
return false;
}
} else {
// unknown type
return false;
}
// known type, not obfuscated
return true;
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright (c) 2021 FabricMC
*
* 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.filament.nameproposal.enigma;
import cuchaz.enigma.api.EnigmaPlugin;
import cuchaz.enigma.api.EnigmaPluginContext;
import cuchaz.enigma.api.service.JarIndexerService;
import cuchaz.enigma.api.service.NameProposalService;
import cuchaz.enigma.api.service.ObfuscationTestService;
public class NameProposalServiceEnigmaPlugin implements EnigmaPlugin {
private static final String ID_PREFIX = "nameproposal:";
@Override
public void init(EnigmaPluginContext ctx) {
ctx.registerService(ID_PREFIX + "intermediary_obfuscation_test", ObfuscationTestService.TYPE, IntermediaryObfuscationTestService::new);
EnigmaNameProposalService service = new EnigmaNameProposalService();
ctx.registerService(ID_PREFIX + "jar_indexer", JarIndexerService.TYPE, ctx1 -> service);
ctx.registerService(ID_PREFIX + "name_proposal", NameProposalService.TYPE, ctx1 -> service);
}
}

View File

@ -0,0 +1 @@
net.fabricmc.filament.nameproposal.enigma.NameProposalServiceEnigmaPlugin

View File

@ -7,7 +7,6 @@ enigma_version=2.5.0
unpick_version=2.3.0
cfr_version=0.2.2
vineflower_version=1.9.3
name_proposal_version=0.2.0
asm_version=9.6
# Javadoc generation/linking