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
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