Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 9 additions & 93 deletions enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
package org.quiltmc.enigma.api;

import com.google.common.base.Preconditions;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.Streams;
import com.google.common.io.MoreFiles;
import org.jspecify.annotations.Nullable;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
import org.quiltmc.enigma.api.analysis.index.jar.CombinedJarIndex;
import org.quiltmc.enigma.api.analysis.index.jar.EntryIndex;
import org.quiltmc.enigma.api.analysis.index.jar.InheritanceIndex;
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.jar.ReferenceIndex;
import org.quiltmc.enigma.api.analysis.index.mapping.MappingsIndex;
import org.quiltmc.enigma.api.class_provider.CachingClassProvider;
import org.quiltmc.enigma.api.class_provider.ClassProvider;
Expand All @@ -23,9 +17,6 @@
import org.quiltmc.enigma.api.class_provider.ObfuscationFixClassProvider;
import org.quiltmc.enigma.api.class_provider.ProjectClassProvider;
import org.quiltmc.enigma.api.translation.mapping.serde.MappingParseException;
import org.quiltmc.enigma.api.translation.representation.entry.ClassEntry;
import org.quiltmc.enigma.api.translation.representation.entry.FieldEntry;
import org.quiltmc.enigma.api.translation.representation.entry.MethodEntry;
import org.quiltmc.enigma.impl.analysis.ClassLoaderClassProvider;
import org.quiltmc.enigma.api.service.EnigmaService;
import org.quiltmc.enigma.api.service.EnigmaServiceContext;
Expand All @@ -40,7 +31,6 @@
import org.quiltmc.enigma.api.translation.mapping.tree.HashEntryTree;
import org.quiltmc.enigma.api.translation.representation.entry.Entry;
import org.quiltmc.enigma.impl.analysis.index.AbstractJarIndex;
import org.quiltmc.enigma.impl.analysis.index.IndexClassVisitor;
import org.quiltmc.enigma.util.Either;
import org.quiltmc.enigma.util.I18n;
import org.quiltmc.enigma.util.Utils;
Expand All @@ -56,20 +46,15 @@
import java.nio.file.attribute.BasicFileAttributes;
import java.sql.DriverManager;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Enigma {
public static final String NAME = "Enigma";
Expand Down Expand Up @@ -119,9 +104,9 @@ public static Builder builder() {

public EnigmaProject openJar(Path path, ClassProvider libraryClassProvider, ProgressListener progress) throws IOException {
JarClassProvider jarClassProvider = new JarClassProvider(path);
AbstractJarIndex jarIndex = MainJarIndex.empty();
AbstractJarIndex libIndex = LibrariesJarIndex.empty();
AbstractJarIndex comboIndex = CombinedJarIndex.empty();
MainJarIndex jarIndex = MainJarIndex.empty();
LibrariesJarIndex libIndex = LibrariesJarIndex.empty();
CombinedJarIndex comboIndex = CombinedJarIndex.empty(jarIndex, libIndex);

ClassLoaderClassProvider jreProvider = new ClassLoaderClassProvider(DriverManager.class.getClassLoader());
ClasspathClassProvider javaClassProvider = new ClasspathClassProvider();
Expand All @@ -130,16 +115,13 @@ public EnigmaProject openJar(Path path, ClassProvider libraryClassProvider, Prog
ProjectClassProvider projectClassProvider = new ProjectClassProvider(mainProjectProvider, librariesProvider);

// main index
this.index(jarIndex, projectClassProvider, progress, "jar", false, null);

// TODO make filtering toggleable with arg once JavaClassProvider is used
final Predicate<String> mainReferencedPredicate = this.createMainReferencedPredicate(jarIndex, projectClassProvider);
this.index(jarIndex, projectClassProvider, progress, "jar", false);

// lib index
this.index(libIndex, projectClassProvider, progress, "jar", true, mainReferencedPredicate);
this.index(libIndex, projectClassProvider, progress, "libs", true);

// combined main and lib index
this.index(comboIndex, projectClassProvider, progress, "combined", true, mainReferencedPredicate);
this.index(comboIndex, projectClassProvider, progress, "combined", true);

// name proposal
var nameProposalServices = this.getNameProposalServices();
Expand Down Expand Up @@ -168,75 +150,11 @@ public EnigmaProject openJar(Path path, ClassProvider libraryClassProvider, Prog
return new EnigmaProject(this, path, mainProjectProvider, jarIndex, libIndex, comboIndex, mappingsIndex, proposedNames, Utils.zipSha1(path));
}

private Predicate<String> createMainReferencedPredicate(AbstractJarIndex mainIndex, ProjectClassProvider classProvider) {
final EntryIndex mainEntryIndex = mainIndex.getIndex(EntryIndex.class);

final EntryIndex entryIndex = new EntryIndex();
final ReferenceIndex referenceIndex = new ReferenceIndex();
final InheritanceIndex inheritanceIndex = new InheritanceIndex(entryIndex);

final Collection<String> allClassNames = classProvider.getClassNames();
for (final String className : allClassNames) {
final ClassNode classNode = Objects.requireNonNull(classProvider.get(className));
classNode.accept(new IndexClassVisitor(entryIndex, Enigma.ASM_VERSION));
classNode.accept(new IndexClassVisitor(referenceIndex, Enigma.ASM_VERSION));
classNode.accept(new IndexClassVisitor(inheritanceIndex, Enigma.ASM_VERSION));
}

return className -> {
final ClassEntry classEntry = new ClassEntry(className);
if (mainEntryIndex.hasClass(classEntry)) {
return true;
}

if (inheritanceIndex.getChildren(classEntry).stream().anyMatch(mainEntryIndex::hasClass)) {
return true;
}

final boolean typeReferenced = Streams
.concat(
referenceIndex.getReferencesToClass(classEntry).stream(),
referenceIndex.getMethodTypeReferencesToClass(classEntry).stream(),
referenceIndex.getFieldTypeReferencesToClass(classEntry).stream()
)
.anyMatch(reference ->
mainEntryIndex.hasClass(reference.entry) || mainEntryIndex.hasEntry(reference.context)
);

if (typeReferenced) {
return true;
}

final List<MethodEntry> mainMethods = mainIndex.getChildrenByClass().values().stream()
.flatMap(entry -> entry instanceof MethodEntry method ? Stream.of(method) : Stream.empty())
.toList();

final boolean methodReferenced = mainMethods.stream()
.flatMap(method -> referenceIndex.getMethodsReferencedBy(method).stream())
.map(MethodEntry::getParent)
.anyMatch(classEntry::equals);
if (methodReferenced) {
return true;
}

// field referenced
return mainMethods.stream()
.flatMap(method -> referenceIndex.getFieldsReferencedBy(method).stream())
.map(FieldEntry::getParent)
.anyMatch(classEntry::equals);
};
}

private void index(
AbstractJarIndex index, ProjectClassProvider classProvider, ProgressListener progress, String progressKey,
boolean includesLibraries, @Nullable Predicate<String> classNameFilter
boolean includesLibraries
) {
if (classNameFilter == null) {
index.indexJar(classProvider, progress);
classNameFilter = Predicates.alwaysTrue();
} else {
index.indexJar(classProvider, progress, classNameFilter);
}
index.indexJar(classProvider, progress);

List<JarIndexerService> indexers = this.services.get(JarIndexerService.TYPE);
progress.init(indexers.size(), I18n.translate("progress." + progressKey + ".custom_indexing"));
Expand All @@ -245,9 +163,7 @@ private void index(
for (var service : indexers) {
if (!(includesLibraries && !service.shouldIndexLibraries())) {
progress.step(i++, I18n.translateFormatted("progress." + progressKey + ".custom_indexing.indexer", service.getId()));
Set<String> scope = index.getIndexableClassNames(classProvider).stream()
.filter(classNameFilter)
.collect(Collectors.toCollection(HashSet::new));
Set<String> scope = new HashSet<>(index.getIndexableClassNames(classProvider));
service.acceptJar(scope, classProvider, index);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,180 +1,40 @@
package org.quiltmc.enigma.api.analysis.index.jar;

import com.google.common.collect.Maps;
import org.jspecify.annotations.Nullable;
import org.quiltmc.enigma.api.translation.representation.AccessFlags;
import org.quiltmc.enigma.api.translation.representation.ArgumentDescriptor;
import org.quiltmc.enigma.api.translation.representation.MethodDescriptor;
import org.quiltmc.enigma.api.translation.representation.TypeDescriptor;
import org.quiltmc.enigma.api.translation.representation.entry.ClassEntry;
import org.quiltmc.enigma.api.translation.representation.entry.MethodDefEntry;
import org.quiltmc.enigma.api.translation.representation.entry.MethodEntry;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class BridgeMethodIndex implements JarIndexer {
private final EntryIndex entryIndex;
private final InheritanceIndex inheritanceIndex;
private final ReferenceIndex referenceIndex;
public sealed interface BridgeMethodIndex extends JarIndexer
permits CombinedBridgeMethodIndex, IndependentBridgeMethodIndex {
void findBridgeMethods();

private final Map<MethodEntry, MethodEntry> bridgeToSpecialized = Maps.newHashMap();
private final Map<MethodEntry, MethodEntry> specializedToBridge = Maps.newHashMap();
boolean isBridgeMethod(MethodEntry entry);

public BridgeMethodIndex(EntryIndex entryIndex, InheritanceIndex inheritanceIndex, ReferenceIndex referenceIndex) {
this.entryIndex = entryIndex;
this.inheritanceIndex = inheritanceIndex;
this.referenceIndex = referenceIndex;
}

public void findBridgeMethods() {
// look for access and bridged methods
for (MethodEntry methodEntry : this.entryIndex.getMethods()) {
MethodDefEntry methodDefEntry = (MethodDefEntry) methodEntry;

AccessFlags access = methodDefEntry.getAccess();
if (access == null || !access.isSynthetic()) {
continue;
}

this.indexSyntheticMethod(methodDefEntry, access);
}
}

@Override
public void processIndex(JarIndex index) {
Map<MethodEntry, MethodEntry> copiedAccessToBridge = new HashMap<>(this.specializedToBridge);

for (Map.Entry<MethodEntry, MethodEntry> entry : copiedAccessToBridge.entrySet()) {
MethodEntry specializedEntry = entry.getKey();
MethodEntry bridgeEntry = entry.getValue();
if (bridgeEntry.getName().equals(specializedEntry.getName())) {
continue;
}

MethodEntry renamedSpecializedEntry = specializedEntry.withName(bridgeEntry.getName());
this.specializedToBridge.put(renamedSpecializedEntry, this.specializedToBridge.get(specializedEntry));
}
}

private void indexSyntheticMethod(MethodDefEntry syntheticMethod, AccessFlags access) {
MethodEntry specializedMethod = this.findSpecializedMethod(syntheticMethod);
if (specializedMethod == null) {
return;
}

if (access.isBridge() || this.isPotentialBridge(syntheticMethod, specializedMethod)) {
this.bridgeToSpecialized.put(syntheticMethod, specializedMethod);
if (this.specializedToBridge.containsKey(specializedMethod)) {
// we already have a bridge for this method, so we keep the one higher in the hierarchy
// can happen with a class inheriting from a superclass with one or more bridge method(s)
MethodEntry bridgeMethod = this.specializedToBridge.get(specializedMethod);
this.specializedToBridge.put(specializedMethod, this.getHigherMethod(syntheticMethod, bridgeMethod));
} else {
this.specializedToBridge.put(specializedMethod, syntheticMethod);
}
}
}

private MethodEntry findSpecializedMethod(MethodEntry method) {
// we want to find all compiler-added methods that directly call another with no processing

// get all the methods that we call
final Collection<MethodEntry> referencedMethods = this.referenceIndex.getMethodsReferencedBy(method);

// is there just one?
if (referencedMethods.size() != 1) {
return null;
}

return referencedMethods.stream().findFirst().orElse(null);
}

private boolean isPotentialBridge(MethodDefEntry bridgeMethod, MethodEntry specializedMethod) {
// Bridge methods only exist for inheritance purposes, if we're private, final, or static, we cannot be inherited
AccessFlags bridgeAccess = bridgeMethod.getAccess();
if (bridgeAccess.isPrivate() || bridgeAccess.isFinal() || bridgeAccess.isStatic()) {
return false;
}

MethodDescriptor bridgeDesc = bridgeMethod.getDesc();
MethodDescriptor specializedDesc = specializedMethod.getDesc();
List<ArgumentDescriptor> bridgeArguments = bridgeDesc.getArgumentDescs();
List<ArgumentDescriptor> specializedArguments = specializedDesc.getArgumentDescs();

// A bridge method will always have the same number of arguments
if (bridgeArguments.size() != specializedArguments.size()) {
return false;
}

// Check that all argument types are bridge-compatible
for (int i = 0; i < bridgeArguments.size(); i++) {
if (!this.areTypesBridgeCompatible(bridgeArguments.get(i), specializedArguments.get(i))) {
return false;
}
}

// Check that the return type is bridge-compatible
return this.areTypesBridgeCompatible(bridgeDesc.getReturnDesc(), specializedDesc.getReturnDesc());
}

private boolean areTypesBridgeCompatible(TypeDescriptor bridgeDesc, TypeDescriptor specializedDesc) {
if (bridgeDesc.equals(specializedDesc)) {
return true;
}

// Either the descs will be equal, or they are both types and different through a generic
if (bridgeDesc.isType() && specializedDesc.isType()) {
ClassEntry bridgeType = bridgeDesc.getTypeEntry();
ClassEntry accessedType = specializedDesc.getTypeEntry();

// If the given types are completely unrelated to each other, this can't be bridge compatible
InheritanceIndex.Relation relation = this.inheritanceIndex.computeClassRelation(accessedType, bridgeType);
return relation != InheritanceIndex.Relation.UNRELATED;
}

return false;
}

// Get the method higher in the hierarchy
private MethodEntry getHigherMethod(MethodEntry bridgeMethod1, MethodEntry bridgeMethod2) {
ClassEntry parent1 = bridgeMethod1.getParent();
ClassEntry parent2 = bridgeMethod2.getParent();
return this.inheritanceIndex.getDescendants(parent1).contains(parent2) ? bridgeMethod1 : bridgeMethod2;
}

public boolean isBridgeMethod(MethodEntry entry) {
return this.bridgeToSpecialized.containsKey(entry);
}

public boolean isSpecializedMethod(MethodEntry entry) {
return this.specializedToBridge.containsKey(entry);
}
boolean isSpecializedMethod(MethodEntry entry);

@Nullable
public MethodEntry getBridgeFromSpecialized(MethodEntry specialized) {
return this.specializedToBridge.get(specialized);
}
MethodEntry getBridgeFromSpecialized(MethodEntry specialized);

public MethodEntry getSpecializedFromBridge(MethodEntry bridge) {
return this.bridgeToSpecialized.get(bridge);
}
MethodEntry getSpecializedFromBridge(MethodEntry bridge);

/** Includes "renamed specialized -> bridge" entries. */
public Map<MethodEntry, MethodEntry> getSpecializedToBridge() {
return Collections.unmodifiableMap(this.specializedToBridge);
}
/**
* Includes "renamed specialized -> bridge" entries.
*/
Map<MethodEntry, MethodEntry> getSpecializedToBridge();

/** Only "bridge -> original name" entries. **/
public Map<MethodEntry, MethodEntry> getBridgeToSpecialized() {
return Collections.unmodifiableMap(this.bridgeToSpecialized);
/**
* Only "bridge -> original name" entries.
*/
Map<MethodEntry, MethodEntry> getBridgeToSpecialized();

@Override
default Class<? extends JarIndexer> getType() {
return BridgeMethodIndex.class;
}

@Override
public String getTranslationKey() {
default String getTranslationKey() {
return "progress.jar.indexing.process.bridge_methods";
}
}
Loading