Skip to content
Draft
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
2 changes: 2 additions & 0 deletions enigma-cli/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ application {
jar.manifest.attributes 'Main-Class': mainClass
}

test.dependsOn(project(':enigma').tasks.named('obfuscateTestInputs'))

publishing {
publications {
"$project.name"(MavenPublication) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class DropInvalidMappingsTest extends CommandTest {
private static final Path LONE_JAR = TestUtil.obfJar("lone_class");
private static final Path INNER_JAR = TestUtil.obfJar("inner_classes");
private static final Path ENUMS_JAR = TestUtil.obfJar("enums");
private static final Path DROP_INVALID_MAPPINGS_JAR = TestUtil.obfJar("z_drop_invalid_mappings");
private static final Path DROP_INVALID_MAPPINGS_JAR = TestUtil.obfJar("drop_invalid_mappings");
private static final Path INPUT_DIR = getResource("/drop_invalid_mappings/input/");
private static final Path EXPECTED_DIR = getResource("/drop_invalid_mappings/expected/");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
import static org.quiltmc.enigma.TestUtil.getResource;

public class SearchMappingsTest {
private static final Path JAR = TestUtil.obfJar("complete");
// private static final Path JAR = TestUtil.obfJar("complete");
private static final Path JAR = TestUtil.obfJar("search_mappings");
private static final Path MAPPINGS = getResource("/search_mappings");

// classes
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
CLASS Z OtherReturnInterface
CLASS f OtherReturnInterface
METHOD a abstractMethod (I)C
ARG 1 intParam
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
CLASS aa OuterClass
CLASS g OuterClass
CLASS a InnerClass
METHOD a getOther ()LZ;
METHOD a getOther ()Lf;
Original file line number Diff line number Diff line change
@@ -1 +1 @@
CLASS t ParamType
CLASS h ParamType
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
CLASS l SelfReturnEnum
METHOD a staticGetArray ()[Ll;
METHOD a staticGet (Ljava/lang/String;)Ll;
CLASS i SelfReturnEnum
METHOD a staticGetArray ()[Li;
METHOD a staticGet (Ljava/lang/String;)Li;
ARG 0 staticStringParam
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
CLASS I
CLASS a
FIELD a privateStringField Ljava/lang/String;
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
CLASS ac
CLASS b
FIELD a floatField F
FIELD a intField I
FIELD a stringField Ljava/lang/String;
Expand Down
3 changes: 3 additions & 0 deletions enigma-cli/src/test/resources/search_mappings/c.mapping
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
CLASS c
METHOD a staticVoidMethod (Lh;)V
ARG 0 staticTypedParam
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
CLASS V
CLASS d
FIELD a recordInt I
FIELD a recordString Ljava/lang/String;
FIELD b PRIVATE_STATIC_FINAL_INT_FIELD I
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
CLASS ak
CLASS e
CLASS b InnerFieldType
3 changes: 0 additions & 3 deletions enigma-cli/src/test/resources/search_mappings/r.mapping

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1 +1 @@
CLASS Q some/packaged/PackagedClass
CLASS j some/packaged/PackagedClass
21 changes: 16 additions & 5 deletions enigma/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,17 @@ static String taskNameFrom(String name) {
return builder.toString()
}


final obfuscateTestInputs = tasks.register('obfuscateTestInputs') {
group = 'test-setup'
}

// Generate obfuscated JARs for tests
// If your test fails for class file version problem with proguard, run gradle with -Dorg.gradle.java.home="<older jdk>" flag
def registerTestJarTasks(String name, String... input) {
String taskName = taskNameFrom(name)
final testJar = tasks.register("${taskName}TestJar", Jar.class) {
group = "test-setup"
group = 'test-setup'
from(sourceSets.test.output) {
include input
}
Expand All @@ -127,21 +132,27 @@ def registerTestJarTasks(String name, String... input) {
: file('src/test/resources/proguard-test.conf')

final testObf = tasks.register("${taskName}TestObf", ProGuardTask) {
group = "test-setup"
group = 'test-setup'

configuration confFileArg

final outDir = project.layout.buildDirectory.map { it.dir('test-obf') }

printmapping(outDir.map { it.dir("${name}.map") })

libraryjars(
[jarfilter: '!**.jar', filter: '!module-info.class'],
"${System.getProperty('java.home')}/jmods/java.base.jmod"
)

injars testJar.map { it.archiveFile }

outjars file("build/test-obf/${name}.jar")
outjars outDir.map { it.dir("${name}.jar") }
}

test.dependsOn(testObf)
tasks.named('obfuscateTestInputs') {
dependsOn(testObf)
}
}

registerTestJarTasks("complete", "org/quiltmc/enigma/input/**/*.class")
Expand Down Expand Up @@ -365,7 +376,7 @@ final testRecommendedImplPluginAgainstBumpedEnigma = tasks
}
}

test.dependsOn(testPreHandlingPluginAgainstCurrentEnigma, testRecommendedImplPluginAgainstBumpedEnigma)
test.dependsOn(obfuscateTestInputs, testPreHandlingPluginAgainstCurrentEnigma, testRecommendedImplPluginAgainstBumpedEnigma)

publishing {
publications {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,17 @@

import org.jspecify.annotations.Nullable;
import org.quiltmc.enigma.api.Enigma;
import org.quiltmc.enigma.api.analysis.index.jar.EntryIndex;
import org.quiltmc.enigma.api.analysis.index.jar.JarIndex;
import org.quiltmc.enigma.api.service.NameProposalService;
import org.quiltmc.enigma.api.source.TokenType;
import org.quiltmc.enigma.api.translation.mapping.EntryMapping;
import org.quiltmc.enigma.api.translation.mapping.EntryRemapper;
import org.quiltmc.enigma.api.translation.representation.entry.ClassDefEntry;
import org.quiltmc.enigma.api.translation.representation.entry.ClassEntry;
import org.quiltmc.enigma.api.translation.mapping.tree.EntryTreeNode;
import org.quiltmc.enigma.api.translation.representation.entry.Entry;
import org.quiltmc.enigma.api.translation.representation.entry.FieldEntry;
import org.quiltmc.enigma.api.translation.representation.entry.MethodEntry;

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

public record RecordComponentProposalService(RecordIndexingVisitor visitor) implements NameProposalService {
Expand All @@ -29,14 +26,18 @@ public Map<Entry<?>, EntryMapping> getProposedNames(Enigma enigma, JarIndex inde

@Nullable
@Override
public Map<Entry<?>, EntryMapping> getDynamicProposedNames(EntryRemapper remapper, @Nullable Entry<?> obfEntry, @Nullable EntryMapping oldMapping, @Nullable EntryMapping newMapping) {
public Map<Entry<?>, EntryMapping> getDynamicProposedNames(
EntryRemapper remapper, @Nullable Entry<?> obfEntry, @Nullable EntryMapping oldMapping,
@Nullable EntryMapping newMapping
) {
if (obfEntry instanceof FieldEntry fieldEntry) {
return this.mapRecordComponentGetter(remapper, fieldEntry.getContainingClass(), fieldEntry, newMapping);
return this.mapRecordComponentGetter(fieldEntry, newMapping);
} else if (obfEntry == null) {
Map<Entry<?>, EntryMapping> mappings = new HashMap<>();
for (var mapping : remapper.getMappings()) {
final Map<Entry<?>, EntryMapping> mappings = new HashMap<>();
for (final EntryTreeNode<EntryMapping> mapping : remapper.getMappings()) {
if (mapping.getEntry() instanceof FieldEntry fieldEntry) {
var getter = this.mapRecordComponentGetter(remapper, fieldEntry.getContainingClass(), fieldEntry, mapping.getValue());
final Map<Entry<?>, EntryMapping> getter =
this.mapRecordComponentGetter(fieldEntry, mapping.getValue());
if (getter != null) {
mappings.putAll(getter);
}
Expand All @@ -50,34 +51,17 @@ public Map<Entry<?>, EntryMapping> getDynamicProposedNames(EntryRemapper remappe
}

@Nullable
private Map<Entry<?>, EntryMapping> mapRecordComponentGetter(EntryRemapper remapper, ClassEntry parent, FieldEntry obfFieldEntry, EntryMapping mapping) {
EntryIndex entryIndex = remapper.getJarIndex().getIndex(EntryIndex.class);
ClassDefEntry parentDef = entryIndex.getDefinition(parent);
var def = entryIndex.getDefinition(obfFieldEntry);
if ((parentDef != null && !parentDef.isRecord()) || (def != null && def.getAccess().isStatic())) {
return null;
}

List<MethodEntry> obfClassMethods = remapper.getJarIndex().getChildrenByClass().get(parentDef).stream()
.filter(e -> e instanceof MethodEntry)
.map(e -> (MethodEntry) e)
.toList();

MethodEntry obfMethodEntry = null;
for (MethodEntry method : obfClassMethods) {
if (this.isGetter(obfFieldEntry, method)) {
obfMethodEntry = method;
break;
}
}

if (obfMethodEntry == null) {
private Map<Entry<?>, EntryMapping> mapRecordComponentGetter(FieldEntry obfFieldEntry, EntryMapping mapping) {
final MethodEntry obfGetter = this.visitor.getComponentGetter(obfFieldEntry);
if (obfGetter == null) {
return null;
}

// remap method to match field
EntryMapping newMapping = mapping.tokenType() == TokenType.OBFUSCATED ? new EntryMapping(null, null, TokenType.OBFUSCATED, null) : this.createMapping(mapping.targetName(), TokenType.DYNAMIC_PROPOSED);
return Map.of(obfMethodEntry, newMapping);
final EntryMapping getterMapping = mapping.tokenType() == TokenType.OBFUSCATED
? EntryMapping.OBFUSCATED
: this.createMapping(mapping.targetName(), TokenType.DYNAMIC_PROPOSED);
return Map.of(obfGetter, getterMapping);
}

@Override
Expand All @@ -89,11 +73,6 @@ public void validateProposedMapping(Entry<?> entry, EntryMapping mapping, boolea
NameProposalService.super.validateProposedMapping(entry, mapping, dynamic);
}

public boolean isGetter(FieldEntry obfFieldEntry, MethodEntry method) {
final MethodEntry getter = this.visitor.getComponentGetter(obfFieldEntry);
return getter != null && getter.equals(method);
}

@Override
public String getId() {
return ID;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,34 @@
import java.util.Set;
import java.util.stream.Stream;

/**
* Indexes records, finding component getters and their corresponding fields.
*
* <p> While component fields can be reliably indexed, there can be uncertainty in determining their corresponding
* getters. Some getters can be definitively determined, some are classified as 'probable getters'
* (probabilistically determined), and some cannot be determined at all.
*
* <p> {@link RecordIndexingService} provides separate methods for accessing getters that are definitive, probabilistic,
* or either.<br>
* Either:
* <ul>
* <li> {@link #getComponentGetter(FieldEntry)}
* <li> {@link #getComponentField(MethodEntry)}
* <li> {@link #streamComponentMethods(ClassEntry)}
* </ul>
* Definite:
* <ul>
* <li> {@link #getDefiniteComponentGetter(FieldEntry)}
* <li> {@link #getDefiniteComponentField(MethodEntry)}
* <li> {@link #streamDefiniteComponentMethods(ClassEntry)}
* </ul>
* Probable:
* <ul>
* <li> {@link #getProbableComponentGetter(FieldEntry)}
* <li> {@link #getProbableComponentField(MethodEntry)}
* <li> {@link #streamProbableComponentMethods(ClassEntry)}
* </ul>
*/
public class RecordIndexingService implements JarIndexerService {
public static final String ID = "enigma:record_component_indexer";

Expand All @@ -21,24 +49,108 @@ public class RecordIndexingService implements JarIndexerService {
this.visitor = visitor;
}

/**
* @return the {@link MethodEntry} representing the getter of the passed {@code componentField},
* or {@code null} if the passed {@code componentField} is not a record component field
* or if its getter could not be determined; returns both
* {@linkplain #getDefiniteComponentGetter(FieldEntry) definitive} and
* {@linkplain #getProbableComponentGetter(FieldEntry) probable} getters
*/
@Nullable
public MethodEntry getComponentGetter(FieldEntry componentField) {
return this.visitor.getComponentGetter(componentField);
}

/**
* @return the {@link FieldEntry} representing the field of the passed {@code componentGetter},
* or {@code null} if the passed {@code componentGetter} is not a record component getter
* or if its field could not be determined; returns both
* {@linkplain #getDefiniteComponentField(MethodEntry) definitive} and
* {@linkplain #getProbableComponentField(MethodEntry) probable} fields
*/
@Nullable
public FieldEntry getComponentField(MethodEntry componentGetter) {
return this.visitor.getComponentField(componentGetter);
}

/**
* @return the definitive {@link MethodEntry} representing the getter of the passed {@code componentField},
* or {@code null} if the passed {@code componentField} is not a record component field
* or if its getter could not be definitively determined
*/
@Nullable
public MethodEntry getDefiniteComponentGetter(FieldEntry componentField) {
return this.visitor.getDefiniteComponentGetter(componentField);
}

/**
* @return the definitive {@link FieldEntry} representing the field of the passed {@code componentGetter},
* or {@code null} if the passed {@code componentGetter} is not a record component getter
* or if its field could not be definitively determined
*/
@Nullable
public FieldEntry getDefiniteComponentField(MethodEntry componentGetter) {
return this.visitor.getDefiniteComponentField(componentGetter);
}

/**
* @return the probable {@link MethodEntry} representing the getter of the passed {@code componentField},
* or {@code null} if the passed {@code componentField} is not a record component field
* or if its getter was not probabilistically determined;
* does not include {@linkplain #getDefiniteComponentGetter(FieldEntry) definitive} getters
*/
@Nullable
public MethodEntry getProbableComponentGetter(FieldEntry componentField) {
return this.visitor.getProbableComponentGetter(componentField);
}

/**
* @return the probably {@link FieldEntry} representing the field of the passed {@code componentGetter},
* or {@code null} if the passed {@code componentGetter} is not a record component getter
* or if its field was not probabilistically determined;
* does not include {@linkplain #getDefiniteComponentField(MethodEntry) definitive} fields
*/
@Nullable
public FieldEntry getProbableComponentField(MethodEntry componentGetter) {
return this.visitor.getProbableComponentField(componentGetter);
}

/**
* @return a {@link Stream} of component fields of the passed {@code recordEntry};
* there's no uncertainty in getter field determination, so all fields are always included;
* if the passed {@code recordEntry} does not represent a record, the stream is empty
*/
public Stream<FieldEntry> streamComponentFields(ClassEntry recordEntry) {
return this.visitor.streamComponentFields(recordEntry);
}

/**
* @return a {@link Stream} of component getter methods of the passed {@code recordEntry};
* includes both {@linkplain #streamDefiniteComponentMethods(ClassEntry) definitive} and
* {@linkplain #streamProbableComponentMethods(ClassEntry) probable} getters;
* if the passed {@code recordEntry} does not represent a record, the stream is empty
*/
public Stream<MethodEntry> streamComponentMethods(ClassEntry recordEntry) {
return this.visitor.streamComponentMethods(recordEntry);
}

/**
* @return a {@link Stream} of definitive component getter methods of the passed {@code recordEntry};
* if the passed {@code recordEntry} does not represent a record, the stream is empty
*/
public Stream<MethodEntry> streamDefiniteComponentMethods(ClassEntry recordEntry) {
return this.visitor.streamDefiniteComponentMethods(recordEntry);
}

/**
* @return a {@link Stream} of probable component getter methods of the passed {@code recordEntry};
* does not include {@linkplain #streamDefiniteComponentMethods(ClassEntry) definitive} getters;
* if the passed {@code recordEntry} does not represent a record, the stream is empty
*/
public Stream<MethodEntry> streamProbableComponentMethods(ClassEntry recordEntry) {
return this.visitor.streamProbableComponentMethods(recordEntry);
}

@Override
public void acceptJar(Set<String> scope, ProjectClassProvider classProvider, JarIndex jarIndex) {
for (String className : scope) {
Expand Down
Loading