Skip to content

Commit

Permalink
work on fixing records (#208)
Browse files Browse the repository at this point in the history
* fix navigator panel ignoring editable types

* start working on record fixes

* class loader working!

* wip work

* this trash i wrote actually works?

* fix trash i wrote that almost worked

* separate library indexing from main indexing (bad)

* cursedness

* Revert "cursedness"

This reverts commit 4ea7bc2.

* allow indexer services to specify whether to apply to libraries in the profile

* polish off a little

* checkstyle

* initial version of ignoring record components

* fix up record component getter mapping

* move record components to a proposal service

* move record components to a proposal service

* add a barebones test for record component proposal

* static

* working a bit

* super rough version accounting for name mismatch

* implement initial proposal and clean up

* add testing for records

* rename usages of EntryMapping.DEFAULT

* checkstyle

* fix API breakage in JarIndexerService

* ignore canonical constructor in param stats

* oops

* remove garb

* checkstyle

* Update enigma/src/main/java/org/quiltmc/enigma/impl/plugin/RecordGetterFindingVisitor.java

Co-authored-by: Iota <[email protected]>

* ignore record equals() param and add test

* what was she cooking

---------

Co-authored-by: Iota <[email protected]>
  • Loading branch information
ix0rai and IotaBread authored Sep 11, 2024
1 parent e9e5f44 commit f3f4139
Show file tree
Hide file tree
Showing 42 changed files with 1,114 additions and 362 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.quiltmc.enigma.api.ProgressListener;
import org.quiltmc.enigma.api.analysis.index.jar.JarIndex;
import org.quiltmc.enigma.api.EnigmaPlugin;
import org.quiltmc.enigma.api.analysis.index.jar.MainJarIndex;
import org.quiltmc.enigma.api.class_provider.CachingClassProvider;
import org.quiltmc.enigma.api.class_provider.ClasspathClassProvider;
import org.quiltmc.enigma.api.class_provider.JarClassProvider;
Expand Down Expand Up @@ -106,7 +107,7 @@ public boolean checkArgumentCount(int length) {
public static JarIndex loadJar(Path jar) throws IOException {
Logger.info("Reading JAR...");
JarClassProvider classProvider = new JarClassProvider(jar);
JarIndex index = JarIndex.empty();
JarIndex index = MainJarIndex.empty();
index.indexJar(classProvider.getClassNames(), new CachingClassProvider(classProvider), ProgressListener.createEmpty());

return index;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ private static void recursiveAddMappings(EntryTree<EntryMapping> mappings, JarIn
Logger.debug("Entry {} {} a mapping", entry, hasMapping ? "has" : "doesn't have");
if (!hasMapping && addMapping) {
Logger.debug("Adding mapping for {}", entry);
mappings.insert(entry, EntryMapping.DEFAULT);
mappings.insert(entry, EntryMapping.OBFUSCATED);
}

List<ParentedEntry<?>> children = index.getChildrenByClass().get(entry);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public void write(DataOutput output) throws IOException {
private static void writeEntryTreeNode(DataOutput output, EntryTreeNode<EntryMapping> node) throws IOException {
PacketHelper.writeEntry(output, node.getEntry(), false);
EntryMapping value = node.getValue();
if (value == null) value = EntryMapping.DEFAULT;
if (value == null) value = EntryMapping.OBFUSCATED;

PacketHelper.writeString(output, value.targetName() != null ? value.targetName() : "");
PacketHelper.writeString(output, value.javadoc() != null ? value.javadoc() : "");
Expand Down
64 changes: 43 additions & 21 deletions enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

import com.google.common.io.MoreFiles;
import org.quiltmc.enigma.api.analysis.index.jar.JarIndex;
import org.quiltmc.enigma.api.analysis.index.jar.LibrariesJarIndex;
import org.quiltmc.enigma.api.analysis.index.jar.MainJarIndex;
import org.quiltmc.enigma.api.analysis.index.mapping.MappingsIndex;
import org.quiltmc.enigma.impl.analysis.ClassLoaderClassProvider;
import org.quiltmc.enigma.api.service.EnigmaService;
import org.quiltmc.enigma.api.service.EnigmaServiceContext;
import org.quiltmc.enigma.api.service.EnigmaServiceFactory;
Expand Down Expand Up @@ -37,8 +40,10 @@
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.sql.DriverManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
Expand Down Expand Up @@ -74,23 +79,22 @@ public static Builder builder() {

public EnigmaProject openJar(Path path, ClassProvider libraryClassProvider, ProgressListener progress) throws IOException {
JarClassProvider jarClassProvider = new JarClassProvider(path);
JarIndex index = JarIndex.empty();
ClassProvider classProvider = new ObfuscationFixClassProvider(new CachingClassProvider(new CombiningClassProvider(jarClassProvider, libraryClassProvider)), index);
Set<String> scope = jarClassProvider.getClassNames();
JarIndex index = MainJarIndex.empty();
JarIndex libIndex = LibrariesJarIndex.empty();

index.indexJar(scope, classProvider, progress);

var indexers = this.services.get(JarIndexerService.TYPE);
progress.init(indexers.size(), I18n.translate("progress.jar.custom_indexing"));
ClassLoaderClassProvider jreProvider = new ClassLoaderClassProvider(DriverManager.class.getClassLoader());
CombiningClassProvider librariesProvider = new CombiningClassProvider(jreProvider, libraryClassProvider);
ClassProvider mainProjectProvider = new ObfuscationFixClassProvider(new CachingClassProvider(jarClassProvider), index);

int i = 1;
for (var service : indexers) {
progress.step(i++, I18n.translateFormatted("progress.jar.custom_indexing.indexer", service.getId()));
service.acceptJar(scope, classProvider, index);
}
Set<String> mainScope = new HashSet<>(mainProjectProvider.getClassNames());
Set<String> librariesScope = new HashSet<>(librariesProvider.getClassNames());

progress.step(i, I18n.translate("progress.jar.custom_indexing.finished"));
// main index
this.index(index, mainProjectProvider, mainScope, progress, false);
// lib index
this.index(libIndex, librariesProvider, librariesScope, progress, true);

// name proposal
var nameProposalServices = this.getNameProposalServices();
progress.init(nameProposalServices.size(), I18n.translate("progress.jar.name_proposal"));

Expand All @@ -117,7 +121,25 @@ public EnigmaProject openJar(Path path, ClassProvider libraryClassProvider, Prog
MappingsIndex mappingsIndex = MappingsIndex.empty();
mappingsIndex.indexMappings(proposedNames, progress);

return new EnigmaProject(this, path, classProvider, index, mappingsIndex, proposedNames, Utils.zipSha1(path));
return new EnigmaProject(this, path, mainProjectProvider, index, libIndex, mappingsIndex, proposedNames, Utils.zipSha1(path));
}

private void index(JarIndex index, ClassProvider classProvider, Set<String> scope, ProgressListener progress, boolean libraries) {
String progressKey = libraries ? "libs" : "jar";
index.indexJar(scope, classProvider, progress);

List<JarIndexerService> indexers = this.services.get(JarIndexerService.TYPE);
progress.init(indexers.size(), I18n.translate("progress." + progressKey + ".custom_indexing"));

int i = 1;
for (var service : indexers) {
if (!(libraries && !service.shouldIndexLibraries())) {
progress.step(i++, I18n.translateFormatted("progress." + progressKey + ".custom_indexing.indexer", service.getId()));
service.acceptJar(scope, classProvider, index);
}
}

progress.step(i, I18n.translate("progress." + progressKey + ".custom_indexing.finished"));
}

public EnigmaProfile getProfile() {
Expand Down Expand Up @@ -172,6 +194,12 @@ public Optional<ReadWriteService> getReadWriteService(Path path) {
return this.parseFileType(path).flatMap(this::getReadWriteService);
}

public static void validatePluginId(String id) {
if (id != null && !id.matches("([a-z0-9_]+):([a-z0-9_]+((/[a-z0-9_]+)+)?)")) {
throw new IllegalArgumentException("Invalid plugin id: \"" + id + "\"\n" + "Refer to Javadoc on EnigmaService#getId for how to properly form a service ID.");
}
}

/**
* Determines the mapping format of the provided path. Checks all formats according to their {@link FileType} file extensions.
* If the path is a directory, it will check the first file in the directory. For directories, defaults to the enigma mappings format.
Expand Down Expand Up @@ -335,7 +363,7 @@ EnigmaServices buildServices() {
}

private void validateRegistration(ImmutableListMultimap<EnigmaServiceType<?>, EnigmaService> services, EnigmaServiceType<?> serviceType, EnigmaService service) {
this.validatePluginId(service.getId());
validatePluginId(service.getId());

for (EnigmaService otherService : services.get(serviceType)) {
// all services
Expand All @@ -355,12 +383,6 @@ private void validateRegistration(ImmutableListMultimap<EnigmaServiceType<?>, En
}
}
}

private void validatePluginId(String id) {
if (!id.matches("([a-z0-9_]+):([a-z0-9_]+((/[a-z0-9_]+)+)?)")) {
throw new IllegalArgumentException("Invalid plugin id: \"" + id + "\"\n" + "Refer to Javadoc on EnigmaService#getId for how to properly form a service ID.");
}
}
}

static {
Expand Down
44 changes: 15 additions & 29 deletions enigma/src/main/java/org/quiltmc/enigma/api/EnigmaProject.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
import org.quiltmc.enigma.api.translation.representation.entry.LocalVariableEntry;
import org.quiltmc.enigma.api.translation.representation.entry.MethodEntry;
import org.quiltmc.enigma.util.I18n;
import org.quiltmc.enigma.util.Pair;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.tree.ClassNode;
import org.tinylog.Logger;
Expand All @@ -41,7 +40,6 @@
import java.io.StringWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
Expand All @@ -54,38 +52,23 @@
import java.util.stream.Stream;

public class EnigmaProject {
private static final List<Pair<String, String>> NON_RENAMABLE_METHODS = new ArrayList<>();

static {
NON_RENAMABLE_METHODS.add(new Pair<>("hashCode", "()I"));
NON_RENAMABLE_METHODS.add(new Pair<>("clone", "()Ljava/lang/Object;"));
NON_RENAMABLE_METHODS.add(new Pair<>("equals", "(Ljava/lang/Object;)Z"));
NON_RENAMABLE_METHODS.add(new Pair<>("finalize", "()V"));
NON_RENAMABLE_METHODS.add(new Pair<>("getClass", "()Ljava/lang/Class;"));
NON_RENAMABLE_METHODS.add(new Pair<>("notify", "()V"));
NON_RENAMABLE_METHODS.add(new Pair<>("notifyAll", "()V"));
NON_RENAMABLE_METHODS.add(new Pair<>("toString", "()Ljava/lang/String;"));
NON_RENAMABLE_METHODS.add(new Pair<>("wait", "()V"));
NON_RENAMABLE_METHODS.add(new Pair<>("wait", "(J)V"));
NON_RENAMABLE_METHODS.add(new Pair<>("wait", "(JI)V"));
}

private final Enigma enigma;

private final Path jarPath;
private final ClassProvider classProvider;
private final JarIndex jarIndex;
private MappingsIndex mappingsIndex;
private final JarIndex libIndex;
private final byte[] jarChecksum;

private EntryRemapper remapper;
private MappingsIndex mappingsIndex;

public EnigmaProject(Enigma enigma, Path jarPath, ClassProvider classProvider, JarIndex jarIndex, MappingsIndex mappingsIndex, EntryTree<EntryMapping> proposedNames, byte[] jarChecksum) {
public EnigmaProject(Enigma enigma, Path jarPath, ClassProvider classProvider, JarIndex jarIndex, JarIndex libIndex, MappingsIndex mappingsIndex, EntryTree<EntryMapping> proposedNames, byte[] jarChecksum) {
Preconditions.checkArgument(jarChecksum.length == 20);
this.enigma = enigma;
this.jarPath = jarPath;
this.classProvider = classProvider;
this.jarIndex = jarIndex;
this.libIndex = libIndex;
this.jarChecksum = jarChecksum;

this.mappingsIndex = mappingsIndex;
Expand Down Expand Up @@ -199,18 +182,17 @@ public boolean isRenamable(Entry<?> obfEntry) {
String name = obfMethodEntry.getName();
String sig = obfMethodEntry.getDesc().toString();

// todo change this to a check if the method is declared in java.lang.Object or java.lang.Record

for (Pair<String, String> pair : NON_RENAMABLE_METHODS) {
if (pair.a().equals(name) && pair.b().equals(sig)) {
return false;
}
// methods declared in object and record are not renamable
// note: compareTo ignores parent, we want that
if (this.libIndex.getChildrenByClass().get(new ClassEntry("java/lang/Object")).stream().anyMatch(c -> c instanceof MethodEntry m && m.compareTo(obfMethodEntry) == 0)
|| this.libIndex.getChildrenByClass().get(new ClassEntry("java/lang/Record")).stream().anyMatch(c -> c instanceof MethodEntry m && m.compareTo(obfMethodEntry) == 0)) {
return false;
}

ClassDefEntry parent = this.jarIndex.getIndex(EntryIndex.class).getDefinition(obfMethodEntry.getParent());
if (parent != null && parent.isEnum()
&& ((name.equals("values") && sig.equals("()[L" + parent.getFullName() + ";"))
|| (name.equals("valueOf") && sig.equals("(Ljava/lang/String;)L" + parent.getFullName() + ";")))) {
|| isEnumValueOfMethod(parent, obfMethodEntry))) {
return false;
}
} else if (obfEntry instanceof LocalVariableEntry localEntry && !localEntry.isArgument()) {
Expand All @@ -220,7 +202,7 @@ public boolean isRenamable(Entry<?> obfEntry) {
ClassDefEntry parent = this.jarIndex.getIndex(EntryIndex.class).getDefinition(method.getParent());

// if this is the valueOf method of an enum class, the argument shouldn't be able to be renamed.
if (parent.isEnum() && method.getName().equals("valueOf") && method.getDesc().toString().equals("(Ljava/lang/String;)L" + parent.getFullName() + ";")) {
if (isEnumValueOfMethod(parent, method)) {
return false;
}
} else if (obfEntry instanceof ClassEntry classEntry && this.isAnonymousOrLocal(classEntry)) {
Expand All @@ -230,6 +212,10 @@ public boolean isRenamable(Entry<?> obfEntry) {
return this.jarIndex.getIndex(EntryIndex.class).hasEntry(obfEntry);
}

private static boolean isEnumValueOfMethod(ClassDefEntry parent, MethodEntry method) {
return parent != null && parent.isEnum() && method.getName().equals("valueOf") && method.getDesc().toString().equals("(Ljava/lang/String;)L" + parent.getFullName() + ";");
}

public boolean isRenamable(EntryReference<Entry<?>, Entry<?>> obfReference) {
return obfReference.isNamed() && this.isRenamable(obfReference.getNameableEntry());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,23 @@
public class EntryIndex implements JarIndexer {
private final EntryTree<EntryMapping> tree = new HashEntryTree<>();

private final Map<ClassEntry, AccessFlags> classes = new HashMap<>();
private final Map<FieldEntry, AccessFlags> fields = new HashMap<>();
private final Map<MethodEntry, AccessFlags> methods = new HashMap<>();
private final Map<ClassEntry, ClassDefEntry> definitions = new HashMap<>();
private final Map<FieldEntry, FieldDefEntry> fieldDefinitions = new HashMap<>();
private final Map<MethodEntry, MethodDefEntry> methodDefinitions = new HashMap<>();
private final Map<ClassEntry, ClassDefEntry> classDefinitions = new HashMap<>();

@Override
public void indexClass(ClassDefEntry classEntry) {
this.definitions.put(classEntry, classEntry);
this.classes.put(classEntry, classEntry.getAccess());
this.classDefinitions.put(classEntry, classEntry);
}

@Override
public void indexMethod(MethodDefEntry methodEntry) {
this.methods.put(methodEntry, methodEntry.getAccess());
this.methodDefinitions.put(methodEntry, methodEntry);
}

@Override
public void indexField(FieldDefEntry fieldEntry) {
this.fields.put(fieldEntry, fieldEntry.getAccess());
this.fieldDefinitions.put(fieldEntry, fieldEntry);
}

@Override
Expand All @@ -58,15 +56,15 @@ public void processIndex(JarIndex index) {
}

public boolean hasClass(ClassEntry entry) {
return this.classes.containsKey(entry);
return this.classDefinitions.containsKey(entry);
}

public boolean hasMethod(MethodEntry entry) {
return this.methods.containsKey(entry);
return this.methodDefinitions.containsKey(entry);
}

public boolean hasField(FieldEntry entry) {
return this.fields.containsKey(entry);
return this.fieldDefinitions.containsKey(entry);
}

public boolean hasEntry(Entry<?> entry) {
Expand All @@ -91,17 +89,20 @@ public boolean hasEntry(Entry<?> entry) {

@Nullable
public AccessFlags getMethodAccess(MethodEntry entry) {
return this.methods.get(entry);
var def = this.methodDefinitions.get(entry);
return def == null ? null : def.getAccess();
}

@Nullable
public AccessFlags getFieldAccess(FieldEntry entry) {
return this.fields.get(entry);
var def = this.fieldDefinitions.get(entry);
return def == null ? null : def.getAccess();
}

@Nullable
public AccessFlags getClassAccess(ClassEntry entry) {
return this.classes.get(entry);
var def = this.classDefinitions.get(entry);
return def == null ? null : def.getAccess();
}

@Nullable
Expand All @@ -119,20 +120,31 @@ public AccessFlags getEntryAccess(Entry<?> entry) {
return null;
}

@Nullable
public ClassDefEntry getDefinition(ClassEntry entry) {
return this.definitions.get(entry);
return this.classDefinitions.get(entry);
}

@Nullable
public MethodDefEntry getDefinition(MethodEntry entry) {
return this.methodDefinitions.get(entry);
}

@Nullable
public FieldDefEntry getDefinition(FieldEntry entry) {
return this.fieldDefinitions.get(entry);
}

public Collection<ClassEntry> getClasses() {
return this.classes.keySet();
return this.classDefinitions.keySet();
}

public Collection<MethodEntry> getMethods() {
return this.methods.keySet();
return this.methodDefinitions.keySet();
}

public Collection<FieldEntry> getFields() {
return this.fields.keySet();
return this.fieldDefinitions.keySet();
}

/**
Expand Down
Loading

0 comments on commit f3f4139

Please sign in to comment.