From 26e6693cf541186c7a8b9251c7ad739fd7c9ea4d Mon Sep 17 00:00:00 2001 From: Matt Date: Sun, 27 Oct 2024 22:29:06 -0400 Subject: [PATCH] Mapping generator API support for variables Only applies to variables defined in the LVT, so no-op if no debug symbols are present --- .../mapping/IntermediateMappings.java | 22 ++- .../mapping/WorkspaceClassRemapper.java | 66 ++++++--- .../mapping/aggregate/AggregatedMappings.java | 137 +++++++++++++++--- .../mapping/gen/MappingGenerator.java | 27 +++- .../gen/filter/ExcludeClassesFilter.java | 8 + .../filter/ExcludeExistingMappedFilter.java | 9 ++ .../filter/ExcludeModifiersNameFilter.java | 7 + .../mapping/gen/filter/ExcludeNameFilter.java | 17 ++- .../gen/filter/IncludeClassesFilter.java | 9 ++ .../gen/filter/IncludeKeywordNameFilter.java | 8 + .../gen/filter/IncludeLongNameFilter.java | 18 ++- .../filter/IncludeModifiersNameFilter.java | 7 + .../mapping/gen/filter/IncludeNameFilter.java | 16 +- .../gen/filter/IncludeNonAsciiNameFilter.java | 14 +- .../filter/IncludeWhitespaceNameFilter.java | 12 ++ .../gen/filter/NameGeneratorFilter.java | 17 +++ .../gen/naming/AlphabetNameGenerator.java | 8 + .../gen/naming/IncrementingNameGenerator.java | 13 ++ .../mapping/gen/naming/NameGenerator.java | 14 ++ .../mapping/gen/MappingGeneratorTest.java | 44 +++++- .../recaf/ui/pane/MappingGeneratorPane.java | 57 +++++++- .../main/resources/translations/en_US.lang | 2 + 22 files changed, 466 insertions(+), 66 deletions(-) diff --git a/recaf-core/src/main/java/software/coley/recaf/services/mapping/IntermediateMappings.java b/recaf-core/src/main/java/software/coley/recaf/services/mapping/IntermediateMappings.java index 0e05771fc..49173b437 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/mapping/IntermediateMappings.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/mapping/IntermediateMappings.java @@ -7,7 +7,14 @@ import software.coley.recaf.services.mapping.data.MethodMapping; import software.coley.recaf.services.mapping.data.VariableMapping; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.TreeSet; /** * Collection of object representations of mappings. @@ -81,8 +88,8 @@ public void addMethod(String ownerName, String desc, String oldName, String newN * Post-mapping method name. */ public void addVariable(String ownerName, String methodName, String methodDesc, - String desc, String oldName, int index, - String newName) { + String desc, String oldName, int index, + String newName) { if (Objects.equals(oldName, newName)) return; // Skip identity mappings String key = varKey(ownerName, methodName, methodDesc); variables.computeIfAbsent(key, n -> new ArrayList<>()) @@ -214,7 +221,7 @@ public String getMappedMethodName(@Nonnull String ownerName, @Nonnull String met @Nullable @Override public String getMappedVariableName(@Nonnull String className, @Nonnull String methodName, @Nonnull String methodDesc, - @Nullable String name, @Nullable String desc, int index) { + @Nullable String name, @Nullable String desc, int index) { List variablesInMethod = getMethodVariableMappings(className, methodName, methodDesc); for (VariableMapping variable : variablesInMethod) { if (equalsOrNull(desc, variable.getDesc()) && equalsOrNull(name, variable.getOldName()) @@ -231,15 +238,16 @@ public IntermediateMappings exportIntermediate() { return this; } - private static String varKey(String ownerName, String methodName, String methodDesc) { - return String.format("%s\t%s\t%s", ownerName, methodName, methodDesc); + @Nonnull + protected static String varKey(@Nonnull String ownerName, @Nonnull String methodName, @Nonnull String methodDesc) { + return ownerName + "\t" + methodName + "\t" + methodDesc; } private static boolean indexEqualsOrOOB(int a, int b) { return a < 0 || b < 0 || a == b; } - private static boolean equalsOrNull(String a, String b) { + private static boolean equalsOrNull(@Nullable String a, @Nullable String b) { return a == null || b == null || a.equals(b); } } diff --git a/recaf-core/src/main/java/software/coley/recaf/services/mapping/WorkspaceClassRemapper.java b/recaf-core/src/main/java/software/coley/recaf/services/mapping/WorkspaceClassRemapper.java index 3c4a51b78..af2232e50 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/mapping/WorkspaceClassRemapper.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/mapping/WorkspaceClassRemapper.java @@ -21,6 +21,7 @@ */ public class WorkspaceClassRemapper extends ClassRemapper { private final WorkspaceBackedRemapper workspaceRemapper; + private String visitedClassName; /** * @param cv @@ -38,8 +39,32 @@ public WorkspaceClassRemapper(@Nullable ClassVisitor cv, @Nonnull Workspace work } @Override - protected MethodVisitor createMethodRemapper(MethodVisitor mv) { - return new BetterMethodRemapper(mv, workspaceRemapper); + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + // Record the class name + visitedClassName = name; + super.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public MethodVisitor visitMethod(int access, @Nonnull String name, @Nonnull String descriptor, + @Nullable String signature, @Nullable String[] exceptions) { + // Adapted from base ClassRemapper implementation. + // This allows us to skip calls to super 'visitMethod' to bypass calls to 'createMethodRemapper' + // since our visitor we want to make needs information accessible here, but not in that method. + String remappedDescriptor = remapper.mapMethodDesc(descriptor); + MethodVisitor mv = + cv == null ? null : cv.visitMethod( + access, + remapper.mapMethodName(className, name, descriptor), + remappedDescriptor, + remapper.mapSignature(signature, false), + exceptions == null ? null : remapper.mapTypes(exceptions)); + return mv == null ? null : new VariableRenamingMethodVisitor(visitedClassName, name, descriptor, mv, workspaceRemapper); + } + + @Override + protected MethodVisitor createMethodRemapper(@Nullable MethodVisitor mv) { + throw new IllegalStateException("Enhanced 'visitMethod(...)' usage required, 'createMethodMapper(...)' should never be called"); } /** @@ -51,10 +76,21 @@ public boolean hasMappingBeenApplied() { /** * {@link MethodRemapper} pointing to enhanced remapping methods to allow for more context-sensitive behavior. + * Method visitor that functions as an adapter for the + * {@link #visitMethod(int, String, String, String, String[]) standard method remapping visitor} + * that additionally supports variable renaming. */ - private class BetterMethodRemapper extends MethodRemapper { - protected BetterMethodRemapper(MethodVisitor methodVisitor, Remapper remapper) { - super(RecafConstants.getAsmVersion(), methodVisitor, remapper); + private class VariableRenamingMethodVisitor extends MethodRemapper { + private final String methodOwner; + private final String methodName; + private final String methodDesc; + + public VariableRenamingMethodVisitor(@Nonnull String methodOwner, @Nonnull String methodName, @Nonnull String methodDesc, + @Nullable MethodVisitor mv, @Nonnull Remapper remapper) { + super(RecafConstants.getAsmVersion(), mv, remapper); + this.methodOwner = methodOwner; + this.methodName = methodName; + this.methodDesc = methodDesc; } @Override @@ -71,28 +107,10 @@ public void visitInvokeDynamicInsn(String name, String descriptor, Handle bootst (Handle) remapper.mapValue(bootstrapMethodHandle), remappedBootstrapMethodArguments); } - } - - /** - * Method visitor that functions as an adapter for the - * {@link #visitMethod(int, String, String, String, String[]) standard method remapping visitor} - * that additionally supports variable renaming. - */ - private class VariableRenamingMethodVisitor extends MethodVisitor { - private final String className; - private final String methodName; - private final String methodDesc; - - public VariableRenamingMethodVisitor(String className, String methodName, String methodDesc, MethodVisitor mv) { - super(RecafConstants.getAsmVersion(), mv); - this.className = className; - this.methodName = methodName; - this.methodDesc = methodDesc; - } @Override public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { - String mappedName = workspaceRemapper.mapVariableName(className, methodName, methodDesc, name, desc, index); + String mappedName = workspaceRemapper.mapVariableName(methodOwner, methodName, methodDesc, name, desc, index); super.visitLocalVariable(mappedName, desc, signature, start, end, index); } } diff --git a/recaf-core/src/main/java/software/coley/recaf/services/mapping/aggregate/AggregatedMappings.java b/recaf-core/src/main/java/software/coley/recaf/services/mapping/aggregate/AggregatedMappings.java index a70aa9a06..cd98a5aa2 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/mapping/aggregate/AggregatedMappings.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/mapping/aggregate/AggregatedMappings.java @@ -1,6 +1,7 @@ package software.coley.recaf.services.mapping.aggregate; import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; import software.coley.recaf.services.mapping.IntermediateMappings; import software.coley.recaf.services.mapping.Mappings; import software.coley.recaf.services.mapping.WorkspaceBackedRemapper; @@ -8,9 +9,14 @@ import software.coley.recaf.services.mapping.data.FieldMapping; import software.coley.recaf.services.mapping.data.MemberMapping; import software.coley.recaf.services.mapping.data.MethodMapping; +import software.coley.recaf.services.mapping.data.VariableMapping; import software.coley.recaf.workspace.model.Workspace; -import java.util.*; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; /** * Mappings implementation for internal tracking of aggregated mappings. @@ -57,7 +63,8 @@ public String map(String internalName) { * @return Original class name. * {@code null} if the class has not been mapped. */ - public String getReverseClassMapping(String name) { + @Nullable + public String getReverseClassMapping(@Nonnull String name) { return reverseOrderClassMapping.get(name); } @@ -72,17 +79,25 @@ public String getReverseClassMapping(String name) { * @return Original name of the field if any mappings exist. * {@code null} if the field has not been mapped. */ - public String getReverseFieldMapping(String owner, String fieldName, String fieldDesc) { + @Nullable + public String getReverseFieldMapping(@Nonnull String owner, @Nonnull String fieldName, @Nonnull String fieldDesc) { String originalOwnerName = getReverseClassMapping(owner); if (originalOwnerName == null) originalOwnerName = owner; + + // Get fields in the original class List fieldMappings = fields.get(originalOwnerName); - if (fieldMappings == null) + if (fieldMappings == null || fieldMappings.isEmpty()) return null; + + // Find a matching field + String originalDesc = reverseMapper.mapDesc(fieldDesc); for (FieldMapping fieldMapping : fieldMappings) { + // The current name must match the mappings "new" name if (!fieldName.equals(fieldMapping.getNewName())) continue; - String originalDesc = reverseMapper.mapDesc(fieldDesc); + + // The original field descriptor must match the mapping key's if (originalDesc.equals(fieldMapping.getDesc())) return fieldMapping.getOldName(); } @@ -100,25 +115,86 @@ public String getReverseFieldMapping(String owner, String fieldName, String fiel * @return Original name of the method if any mappings exist. * {@code null} if the method has not been mapped. */ - public String getReverseMethodMapping(String owner, String methodName, String methodDesc) { + @Nullable + public String getReverseMethodMapping(@Nonnull String owner, @Nonnull String methodName, @Nonnull String methodDesc) { String originalOwnerName = getReverseClassMapping(owner); if (originalOwnerName == null) originalOwnerName = owner; + + // Get methods in the original class List methodMappings = methods.get(originalOwnerName); - if (methodMappings == null) + if (methodMappings == null || methodMappings.isEmpty()) return null; + + // Find a matching method + String originalDesc = reverseMapper.mapDesc(methodDesc); for (MethodMapping methodMapping : methodMappings) { + // The current name must match the mappings "new" name if (!methodName.equals(methodMapping.getNewName())) continue; - String originalDesc = reverseMapper.mapDesc(methodDesc); + + // The original method descriptor must match the mapping key's if (originalDesc.equals(methodMapping.getDesc())) return methodMapping.getOldName(); } return null; } + /** + * @param owner + * Current class name. + * @param methodName + * Current method name. + * @param methodDesc + * Current method descriptor. + * @param varName + * Current variable name. + * @param varDesc + * Current variable descriptor. + * @param varIndex + * Variable index. + * + * @return Original name of the variable if any mappings exist. + * {@code null} if the variable has not been mapped. + */ + @Nullable + public String getReverseVariableMapping(@Nonnull String owner, @Nonnull String methodName, @Nonnull String methodDesc, + @Nonnull String varName, @Nonnull String varDesc, int varIndex) { + String originalOwnerName = getReverseClassMapping(owner); + if (originalOwnerName == null) + originalOwnerName = owner; + + // Get methods in the original class + List methodMappings = methods.get(originalOwnerName); + if (methodMappings == null || methodMappings.isEmpty()) + return null; + + String originalMethodDesc = reverseMapper.mapDesc(methodDesc); + String originalVarDesc = reverseMapper.mapDesc(varDesc); + for (MethodMapping methodMapping : methodMappings) { + // The current name must match the mappings "new" name + if (!methodName.equals(methodMapping.getNewName())) + continue; + + // The original method descriptor must match the mapping key's + if (originalMethodDesc.equals(methodMapping.getDesc())) { + // Get the variables that were mapped under the original name + List variableMappings = variables.get(varKey(originalOwnerName, methodMapping.getOldName(), originalMethodDesc)); + for (VariableMapping variableMapping : variableMappings) { + // If the variable index, name, and descriptor match, yield the variable mapping's original name + if (variableMapping.getIndex() == varIndex && variableMapping.getNewName().equals(varName)) { + if (varDesc.equals(originalVarDesc)) { + return variableMapping.getOldName(); + } + } + } + } + } + return null; + } + @Override - public void addClass(String oldName, String newName) { + public void addClass(@Nonnull String oldName, @Nonnull String newName) { super.addClass(oldName, newName); reverseOrderClassMapping.put(newName, oldName); } @@ -142,7 +218,7 @@ public void clear() { * * @return {@code true} when the mapping operation required bridging a current class name to its original name. */ - public boolean update(Mappings newMappings) { + public boolean update(@Nonnull Mappings newMappings) { // ORIGINAL: // a -> b // a.f1 -> b.f2 @@ -157,6 +233,7 @@ public boolean update(Mappings newMappings) { bridged = updateClasses(intermediate.getClasses()); bridged |= updateMembers(intermediate.getFields().values()); bridged |= updateMembers(intermediate.getMethods().values()); + bridged |= updateVariables(intermediate.getVariables().values()); return bridged; } @@ -168,7 +245,7 @@ private boolean updateClasses(Map classes) { String aName = reverseOrderClassMapping.get(bName); if (aName != null) { // There is a prior entry of the class, 'aName' thus we use it as the key - // and not 'bName' since that was the prior value the mapping for 'aName. + // and not 'bName' since that was the prior value the mapping for 'aName'. bridged = true; addClass(aName, cName); } else { @@ -179,27 +256,26 @@ private boolean updateClasses(Map classes) { return bridged; } - private boolean updateMembers(Collection> newMappings) { // With members, we need to take special care, for example: // 1. a --> b // 2. b.x --> b.y - // Now we need to ensure the mapping "a.x --> y" exists. + // Now we need to ensure the mapping "a.x --> b.y" exists. boolean bridged = false; for (List members : newMappings) { for (MemberMapping newMemberMapping : members) { - String bName = newMemberMapping.getOwnerName(); - String aName = reverseOrderClassMapping.get(bName); + String bOwnerName = newMemberMapping.getOwnerName(); + String aOwnerName = reverseOrderClassMapping.get(bOwnerName); String oldMemberName = newMemberMapping.getOldName(); String newMemberName = newMemberMapping.getNewName(); String desc = newMemberMapping.getDesc(); - String owner = bName; - if (aName != null) { + String owner = bOwnerName; + if (aOwnerName != null) { // We need to map the member current mapped owner name to the // original owner's name. bridged = true; - owner = aName; - oldMemberName = findPriorMemberName(aName, newMemberMapping); + owner = aOwnerName; + oldMemberName = findPriorMemberName(aOwnerName, newMemberMapping); } // Desc must always be checked for updates @@ -216,6 +292,29 @@ private boolean updateMembers(Collection> newM return bridged; } + private boolean updateVariables(Collection> newMappings) { + // a.foo() var x + // ... + // a.foo() --> b.foo() + // b.foo() --> b.bar() + // b.bar() var x --> var z + // ... + // a.foo() var x --> var z + boolean bridged = false; + + /* + TODO: Aggregate variable mappings + for (List variableMappings : newMappings) { + for (VariableMapping newVariableMapping : variableMappings) { + String bOwner = newVariableMapping.getOwnerName(); + String bMethodName = newVariableMapping.getMethodName(); + String bMethodDesc = newVariableMapping.getMethodDesc(); + } + } + */ + return bridged; + } + private String applyReverseMappings(String desc) { if (desc == null) return null; diff --git a/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/MappingGenerator.java b/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/MappingGenerator.java index 19ef38917..f373d7303 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/MappingGenerator.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/MappingGenerator.java @@ -7,6 +7,7 @@ import software.coley.recaf.info.ClassInfo; import software.coley.recaf.info.member.ClassMember; import software.coley.recaf.info.member.FieldMember; +import software.coley.recaf.info.member.LocalVariable; import software.coley.recaf.info.member.MethodMember; import software.coley.recaf.services.Service; import software.coley.recaf.services.inheritance.InheritanceGraph; @@ -20,7 +21,12 @@ import software.coley.recaf.workspace.model.bundle.Bundle; import software.coley.recaf.workspace.model.resource.WorkspaceResource; -import java.util.*; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; /** * Mapping generator. @@ -186,6 +192,25 @@ private void generateFamilyMappings(@Nonnull MappingsAdapter mappings, @Nonnull if (libraryMethods.contains(key) || mappings.getMappedMethodName(ownerName, methodName, methodDesc) != null) continue; + // Create variable mappings + for (LocalVariable variable : method.getLocalVariables()) { + String variableName = variable.getName(); + + // Do not rename 'this' local variable... Unless its not "this" then force it to be "this" + if (variable.getIndex() == 0 && !method.hasStaticModifier()) { + if (!"this".equals(variableName)) + mappings.addVariable(ownerName, methodName, methodDesc, variableName, variable.getDescriptor(), variable.getIndex(), "this"); + continue; + } + + if (filter.shouldMapLocalVariable(owner, method, variable)) { + String mappedVariableName = generator.mapVariable(owner, method, variable); + if (!mappedVariableName.equals(variableName)) { + mappings.addVariable(ownerName, methodName, methodDesc, variableName, variable.getDescriptor(), variable.getIndex(), mappedVariableName); + } + } + } + // Create mapped name and record into mappings. String mappedMethodName = generator.mapMethod(owner, method); diff --git a/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/ExcludeClassesFilter.java b/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/ExcludeClassesFilter.java index fa77cbea6..d391c5486 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/ExcludeClassesFilter.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/ExcludeClassesFilter.java @@ -4,6 +4,7 @@ import jakarta.annotation.Nullable; import software.coley.recaf.info.ClassInfo; import software.coley.recaf.info.member.FieldMember; +import software.coley.recaf.info.member.LocalVariable; import software.coley.recaf.info.member.MethodMember; import software.coley.recaf.services.search.match.StringPredicate; @@ -44,4 +45,11 @@ public boolean shouldMapMethod(@Nonnull ClassInfo owner, @Nonnull MethodMember m // Consider owner type, we do not want to map methods if they are inside the exclusion filter return shouldMapClass(owner) && super.shouldMapMethod(owner, method); } + + @Override + public boolean shouldMapLocalVariable(@Nonnull ClassInfo owner, @Nonnull MethodMember declaringMethod, @Nonnull LocalVariable variable) { + return shouldMapClass(owner) + && super.shouldMapMethod(owner, declaringMethod) + && super.shouldMapLocalVariable(owner, declaringMethod, variable); + } } diff --git a/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/ExcludeExistingMappedFilter.java b/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/ExcludeExistingMappedFilter.java index e7e71efcb..f9eb3bcbb 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/ExcludeExistingMappedFilter.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/ExcludeExistingMappedFilter.java @@ -4,6 +4,7 @@ import jakarta.annotation.Nullable; import software.coley.recaf.info.ClassInfo; import software.coley.recaf.info.member.FieldMember; +import software.coley.recaf.info.member.LocalVariable; import software.coley.recaf.info.member.MethodMember; import software.coley.recaf.services.mapping.aggregate.AggregatedMappings; @@ -46,4 +47,12 @@ public boolean shouldMapMethod(@Nonnull ClassInfo owner, @Nonnull MethodMember m return false; return super.shouldMapMethod(owner, method); } + + @Override + public boolean shouldMapLocalVariable(@Nonnull ClassInfo owner, @Nonnull MethodMember declaringMethod, @Nonnull LocalVariable variable) { + if (aggregate.getReverseVariableMapping(owner.getName(), declaringMethod.getName(), + declaringMethod.getDescriptor(), variable.getName(), variable.getDescriptor(), variable.getIndex()) != null) + return false; + return super.shouldMapLocalVariable(owner, declaringMethod, variable); + } } diff --git a/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/ExcludeModifiersNameFilter.java b/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/ExcludeModifiersNameFilter.java index 438626762..5cca5e184 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/ExcludeModifiersNameFilter.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/ExcludeModifiersNameFilter.java @@ -4,6 +4,7 @@ import jakarta.annotation.Nullable; import software.coley.recaf.info.ClassInfo; import software.coley.recaf.info.member.FieldMember; +import software.coley.recaf.info.member.LocalVariable; import software.coley.recaf.info.member.MethodMember; import java.util.Collection; @@ -61,4 +62,10 @@ public boolean shouldMapMethod(@Nonnull ClassInfo owner, @Nonnull MethodMember m return false; return super.shouldMapMethod(owner, method); } + + @Override + public boolean shouldMapLocalVariable(@Nonnull ClassInfo owner, @Nonnull MethodMember declaringMethod, @Nonnull LocalVariable variable) { + // Variables are not targeted, so delegate to next filter + return super.shouldMapLocalVariable(owner, declaringMethod, variable); + } } diff --git a/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/ExcludeNameFilter.java b/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/ExcludeNameFilter.java index d6e7cdcd6..1a0ee1801 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/ExcludeNameFilter.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/ExcludeNameFilter.java @@ -4,11 +4,12 @@ import jakarta.annotation.Nullable; import software.coley.recaf.info.ClassInfo; import software.coley.recaf.info.member.FieldMember; +import software.coley.recaf.info.member.LocalVariable; import software.coley.recaf.info.member.MethodMember; import software.coley.recaf.services.search.match.StringPredicate; /** - * Filter that excludes classes, fields, and methods by their names. + * Filter that excludes classes, fields, methods, and variables by their names. * * @author Matt Coley * @see IncludeNameFilter @@ -17,6 +18,7 @@ public class ExcludeNameFilter extends NameGeneratorFilter { private final StringPredicate classPredicate; private final StringPredicate fieldPredicate; private final StringPredicate methodPredicate; + private final StringPredicate variablePredicate; /** * @param next @@ -30,13 +32,18 @@ public class ExcludeNameFilter extends NameGeneratorFilter { * @param methodPredicate * Method name predicate for included names. * {@code null} to skip filtering for method names. + * @param variablePredicate + * Variable name predicate for included names. + * {@code null} to skip filtering for variable names. */ public ExcludeNameFilter(@Nullable NameGeneratorFilter next, @Nullable StringPredicate classPredicate, - @Nullable StringPredicate fieldPredicate, @Nullable StringPredicate methodPredicate) { + @Nullable StringPredicate fieldPredicate, @Nullable StringPredicate methodPredicate, + @Nullable StringPredicate variablePredicate) { super(next, true); this.classPredicate = classPredicate; this.fieldPredicate = fieldPredicate; this.methodPredicate = methodPredicate; + this.variablePredicate = variablePredicate; } @Override @@ -56,4 +63,10 @@ public boolean shouldMapMethod(@Nonnull ClassInfo owner, @Nonnull MethodMember m return super.shouldMapMethod(owner, method) && !(methodPredicate != null && methodPredicate.match(method.getName())); } + + @Override + public boolean shouldMapLocalVariable(@Nonnull ClassInfo owner, @Nonnull MethodMember declaringMethod, @Nonnull LocalVariable variable) { + return super.shouldMapLocalVariable(owner, declaringMethod, variable) && + !(variablePredicate != null && variablePredicate.match(variable.getName())); + } } diff --git a/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/IncludeClassesFilter.java b/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/IncludeClassesFilter.java index 85a983ae8..b44b640f1 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/IncludeClassesFilter.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/IncludeClassesFilter.java @@ -4,6 +4,7 @@ import jakarta.annotation.Nullable; import software.coley.recaf.info.ClassInfo; import software.coley.recaf.info.member.FieldMember; +import software.coley.recaf.info.member.LocalVariable; import software.coley.recaf.info.member.MethodMember; import software.coley.recaf.services.search.match.StringPredicate; @@ -44,4 +45,12 @@ public boolean shouldMapMethod(@Nonnull ClassInfo owner, @Nonnull MethodMember m // Consider owner type, we do not want to map methods if they are outside the inclusion filter return shouldMapClass(owner) && super.shouldMapMethod(owner, method); } + + @Override + public boolean shouldMapLocalVariable(@Nonnull ClassInfo owner, @Nonnull MethodMember declaringMethod, + @Nonnull LocalVariable variable) { + // Consider owner type and method, we do not want to map variables if they are outside the inclusion filter + return shouldMapClass(owner) && super.shouldMapMethod(owner, declaringMethod) + && super.shouldMapLocalVariable(owner, declaringMethod, variable); + } } diff --git a/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/IncludeKeywordNameFilter.java b/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/IncludeKeywordNameFilter.java index ed67e438c..e5c549cc3 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/IncludeKeywordNameFilter.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/IncludeKeywordNameFilter.java @@ -4,6 +4,7 @@ import jakarta.annotation.Nullable; import software.coley.recaf.info.ClassInfo; import software.coley.recaf.info.member.FieldMember; +import software.coley.recaf.info.member.LocalVariable; import software.coley.recaf.info.member.MethodMember; import software.coley.recaf.util.StringUtil; @@ -48,4 +49,11 @@ public boolean shouldMapMethod(@Nonnull ClassInfo owner, @Nonnull MethodMember i return true; return super.shouldMapMethod(owner, info); } + + @Override + public boolean shouldMapLocalVariable(@Nonnull ClassInfo owner, @Nonnull MethodMember declaringMethod, @Nonnull LocalVariable variable) { + if (getKeywords().contains(variable.getName())) + return true; + return super.shouldMapLocalVariable(owner, declaringMethod, variable); + } } diff --git a/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/IncludeLongNameFilter.java b/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/IncludeLongNameFilter.java index 2d8a894bd..d5381df57 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/IncludeLongNameFilter.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/IncludeLongNameFilter.java @@ -5,6 +5,7 @@ import software.coley.recaf.info.ClassInfo; import software.coley.recaf.info.member.ClassMember; import software.coley.recaf.info.member.FieldMember; +import software.coley.recaf.info.member.LocalVariable; import software.coley.recaf.info.member.MethodMember; /** @@ -17,6 +18,7 @@ public class IncludeLongNameFilter extends NameGeneratorFilter { private final boolean targetClasses; private final boolean targetFields; private final boolean targetMethods; + private final boolean targetVariables; /** * @param next @@ -29,14 +31,17 @@ public class IncludeLongNameFilter extends NameGeneratorFilter { * Check against fields. * @param targetMethods * Check against methods. + * @param targetVariables + * Check against variables. */ public IncludeLongNameFilter(@Nullable NameGeneratorFilter next, int maxNameLength, - boolean targetClasses, boolean targetFields, boolean targetMethods) { + boolean targetClasses, boolean targetFields, boolean targetMethods, boolean targetVariables) { super(next, false); this.maxNameLength = maxNameLength; this.targetClasses = targetClasses; this.targetFields = targetFields; this.targetMethods = targetMethods; + this.targetVariables = targetVariables; } @Override @@ -60,6 +65,13 @@ public boolean shouldMapMethod(@Nonnull ClassInfo owner, @Nonnull MethodMember m return super.shouldMapMethod(owner, method); } + @Override + public boolean shouldMapLocalVariable(@Nonnull ClassInfo owner, @Nonnull MethodMember declaringMethod, @Nonnull LocalVariable variable) { + if (targetVariables && shouldMap(variable)) + return true; + return super.shouldMapLocalVariable(owner, declaringMethod, variable); + } + private boolean shouldMap(ClassInfo info) { return shouldMap(info.getName()); } @@ -68,6 +80,10 @@ private boolean shouldMap(ClassMember info) { return shouldMap(info.getName()); } + private boolean shouldMap(LocalVariable info) { + return shouldMap(info.getName()); + } + private boolean shouldMap(String name) { return name.length() > maxNameLength; } diff --git a/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/IncludeModifiersNameFilter.java b/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/IncludeModifiersNameFilter.java index 7805d2aa0..cca8b6ef3 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/IncludeModifiersNameFilter.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/IncludeModifiersNameFilter.java @@ -4,6 +4,7 @@ import jakarta.annotation.Nullable; import software.coley.recaf.info.ClassInfo; import software.coley.recaf.info.member.FieldMember; +import software.coley.recaf.info.member.LocalVariable; import software.coley.recaf.info.member.MethodMember; import java.util.Collection; @@ -61,4 +62,10 @@ public boolean shouldMapMethod(@Nonnull ClassInfo owner, @Nonnull MethodMember m return true; return super.shouldMapMethod(owner, method); } + + @Override + public boolean shouldMapLocalVariable(@Nonnull ClassInfo owner, @Nonnull MethodMember declaringMethod, @Nonnull LocalVariable variable) { + // Variables are not targeted, so delegate to next filter + return super.shouldMapLocalVariable(owner, declaringMethod, variable); + } } diff --git a/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/IncludeNameFilter.java b/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/IncludeNameFilter.java index 17e92e4df..d22d6a14e 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/IncludeNameFilter.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/IncludeNameFilter.java @@ -4,6 +4,7 @@ import jakarta.annotation.Nullable; import software.coley.recaf.info.ClassInfo; import software.coley.recaf.info.member.FieldMember; +import software.coley.recaf.info.member.LocalVariable; import software.coley.recaf.info.member.MethodMember; import software.coley.recaf.services.search.match.StringPredicate; @@ -17,6 +18,7 @@ public class IncludeNameFilter extends NameGeneratorFilter { private final StringPredicate classPredicate; private final StringPredicate fieldPredicate; private final StringPredicate methodPredicate; + private final StringPredicate variablePredicate; /** * @param next @@ -30,13 +32,18 @@ public class IncludeNameFilter extends NameGeneratorFilter { * @param methodPredicate * Method name predicate for excluded names. * {@code null} to skip filtering for method names. + * @param variablePredicate + * Variable name predicate for excluded names. + * {@code null} to skip filtering for variable names. */ public IncludeNameFilter(@Nullable NameGeneratorFilter next, @Nullable StringPredicate classPredicate, - @Nullable StringPredicate fieldPredicate, @Nullable StringPredicate methodPredicate) { + @Nullable StringPredicate fieldPredicate, @Nullable StringPredicate methodPredicate, + @Nullable StringPredicate variablePredicate) { super(next, false); this.classPredicate = classPredicate; this.fieldPredicate = fieldPredicate; this.methodPredicate = methodPredicate; + this.variablePredicate = variablePredicate; } @Override @@ -59,4 +66,11 @@ public boolean shouldMapMethod(@Nonnull ClassInfo owner, @Nonnull MethodMember m return true; return super.shouldMapMethod(owner, method); } + + @Override + public boolean shouldMapLocalVariable(@Nonnull ClassInfo owner, @Nonnull MethodMember declaringMethod, @Nonnull LocalVariable variable) { + if (variablePredicate != null && variablePredicate.match(variable.getName())) + return true; + return super.shouldMapLocalVariable(owner, declaringMethod, variable); + } } diff --git a/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/IncludeNonAsciiNameFilter.java b/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/IncludeNonAsciiNameFilter.java index 8b0777940..4b0c12b44 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/IncludeNonAsciiNameFilter.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/IncludeNonAsciiNameFilter.java @@ -5,6 +5,7 @@ import software.coley.recaf.info.ClassInfo; import software.coley.recaf.info.member.ClassMember; import software.coley.recaf.info.member.FieldMember; +import software.coley.recaf.info.member.LocalVariable; import software.coley.recaf.info.member.MethodMember; /** @@ -42,6 +43,13 @@ public boolean shouldMapMethod(@Nonnull ClassInfo owner, @Nonnull MethodMember m return super.shouldMapMethod(owner, method); } + @Override + public boolean shouldMapLocalVariable(@Nonnull ClassInfo owner, @Nonnull MethodMember declaringMethod, @Nonnull LocalVariable variable) { + if (shouldMap(variable)) + return true; + return super.shouldMapLocalVariable(owner, declaringMethod, variable); + } + private static boolean shouldMap(ClassInfo info) { return shouldMap(info.getName()); } @@ -50,8 +58,12 @@ private static boolean shouldMap(ClassMember info) { return shouldMap(info.getName()); } + private static boolean shouldMap(LocalVariable info) { + return shouldMap(info.getName()); + } + private static boolean shouldMap(String name) { return name.codePoints() - .anyMatch(code -> (code < 0x21 || code > 0x7A)); + .anyMatch(code -> (code < 0x21 || code > 0x7A) || Character.isWhitespace(code)); } } diff --git a/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/IncludeWhitespaceNameFilter.java b/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/IncludeWhitespaceNameFilter.java index a2a903507..c51c39e99 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/IncludeWhitespaceNameFilter.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/IncludeWhitespaceNameFilter.java @@ -5,6 +5,7 @@ import software.coley.recaf.info.ClassInfo; import software.coley.recaf.info.member.ClassMember; import software.coley.recaf.info.member.FieldMember; +import software.coley.recaf.info.member.LocalVariable; import software.coley.recaf.info.member.MethodMember; import software.coley.recaf.util.EscapeUtil; @@ -43,6 +44,13 @@ public boolean shouldMapMethod(@Nonnull ClassInfo owner, @Nonnull MethodMember m return super.shouldMapMethod(owner, method); } + @Override + public boolean shouldMapLocalVariable(@Nonnull ClassInfo owner, @Nonnull MethodMember declaringMethod, @Nonnull LocalVariable variable) { + if (shouldMap(variable)) + return true; + return super.shouldMapLocalVariable(owner, declaringMethod, variable); + } + private static boolean shouldMap(ClassInfo info) { return shouldMap(info.getName()); } @@ -51,6 +59,10 @@ private static boolean shouldMap(ClassMember member) { return shouldMap(member.getName()); } + private static boolean shouldMap(LocalVariable variable) { + return shouldMap(variable.getName()); + } + private static boolean shouldMap(String name) { return EscapeUtil.containsWhitespace(name); } diff --git a/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/NameGeneratorFilter.java b/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/NameGeneratorFilter.java index 4dbf711a6..51acb03e4 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/NameGeneratorFilter.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/filter/NameGeneratorFilter.java @@ -4,6 +4,7 @@ import jakarta.annotation.Nullable; import software.coley.recaf.info.ClassInfo; import software.coley.recaf.info.member.FieldMember; +import software.coley.recaf.info.member.LocalVariable; import software.coley.recaf.info.member.MethodMember; /** @@ -77,4 +78,20 @@ public boolean shouldMapMethod(@Nonnull ClassInfo owner, @Nonnull MethodMember m else return next != null && next.shouldMapMethod(owner, method); } + + /** + * @param owner + * Class the method is defined in. + * @param declaringMethod + * Method the variable is defined in. + * @param variable Variable to check. + * + * @return {@code true} if the generator should create a new name for the method. + */ + public boolean shouldMapLocalVariable(@Nonnull ClassInfo owner, @Nonnull MethodMember declaringMethod, @Nonnull LocalVariable variable) { + if (defaultMap) + return next == null || next.shouldMapLocalVariable(owner, declaringMethod, variable); + else + return next != null && next.shouldMapLocalVariable(owner, declaringMethod, variable); + } } diff --git a/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/naming/AlphabetNameGenerator.java b/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/naming/AlphabetNameGenerator.java index 83316b4a7..0be7c3bf3 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/naming/AlphabetNameGenerator.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/naming/AlphabetNameGenerator.java @@ -4,6 +4,7 @@ import jakarta.annotation.Nullable; import software.coley.recaf.info.ClassInfo; import software.coley.recaf.info.member.FieldMember; +import software.coley.recaf.info.member.LocalVariable; import software.coley.recaf.info.member.MethodMember; import software.coley.recaf.util.StringUtil; import software.coley.recaf.workspace.model.Workspace; @@ -67,4 +68,11 @@ public String mapField(@Nonnull ClassInfo owner, @Nonnull FieldMember field) { public String mapMethod(@Nonnull ClassInfo owner, @Nonnull MethodMember method) { return name(owner.getName() + "#" + method.getName()); } + + @Nonnull + @Override + public String mapVariable(@Nonnull ClassInfo owner, @Nonnull MethodMember declaringMethod, @Nonnull LocalVariable variable) { + return name(owner.getName() + "#" + declaringMethod.getName() + "#" + variable.getIndex() + + "#" + variable.getName() + "#" + variable.getDescriptor()); + } } diff --git a/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/naming/IncrementingNameGenerator.java b/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/naming/IncrementingNameGenerator.java index 713c146cc..37e753631 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/naming/IncrementingNameGenerator.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/naming/IncrementingNameGenerator.java @@ -4,6 +4,7 @@ import jakarta.annotation.Nullable; import software.coley.recaf.info.ClassInfo; import software.coley.recaf.info.member.FieldMember; +import software.coley.recaf.info.member.LocalVariable; import software.coley.recaf.info.member.MethodMember; import software.coley.recaf.workspace.model.Workspace; @@ -18,6 +19,7 @@ public class IncrementingNameGenerator implements DeconflictingNameGenerator { private long classIndex = 1; private long fieldIndex = 1; private long methodIndex = 1; + private long varIndex = 1; @Nonnull private String nextClassName() { @@ -34,6 +36,11 @@ private String nextMethodName() { return "method" + methodIndex++; } + @Nonnull + private String nextVarName() { + return "var" + varIndex++; + } + @Override public void setWorkspace(@Nullable Workspace workspace) { this.workspace = workspace; @@ -69,4 +76,10 @@ public String mapMethod(@Nonnull ClassInfo owner, @Nonnull MethodMember method) name = nextMethodName(); return name; } + + @Nonnull + @Override + public String mapVariable(@Nonnull ClassInfo owner, @Nonnull MethodMember declaringMethod, @Nonnull LocalVariable variable) { + return nextVarName(); + } } diff --git a/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/naming/NameGenerator.java b/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/naming/NameGenerator.java index aa6e4cc01..58c88e00a 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/naming/NameGenerator.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/mapping/gen/naming/NameGenerator.java @@ -3,6 +3,7 @@ import jakarta.annotation.Nonnull; import software.coley.recaf.info.ClassInfo; import software.coley.recaf.info.member.FieldMember; +import software.coley.recaf.info.member.LocalVariable; import software.coley.recaf.info.member.MethodMember; /** @@ -41,4 +42,17 @@ public interface NameGenerator { */ @Nonnull String mapMethod(@Nonnull ClassInfo owner, @Nonnull MethodMember method); + + /** + * @param owner + * Class the method is defined in. + * @param declaringMethod + * Method the variable is defined in. + * @param variable + * Variable to rename. + * + * @return New variable name. + */ + @Nonnull + String mapVariable(@Nonnull ClassInfo owner, @Nonnull MethodMember declaringMethod, @Nonnull LocalVariable variable); } diff --git a/recaf-core/src/test/java/software/coley/recaf/services/mapping/gen/MappingGeneratorTest.java b/recaf-core/src/test/java/software/coley/recaf/services/mapping/gen/MappingGeneratorTest.java index 98c1423e7..fa3836929 100644 --- a/recaf-core/src/test/java/software/coley/recaf/services/mapping/gen/MappingGeneratorTest.java +++ b/recaf-core/src/test/java/software/coley/recaf/services/mapping/gen/MappingGeneratorTest.java @@ -7,18 +7,28 @@ import org.objectweb.asm.Opcodes; import software.coley.recaf.info.ClassInfo; import software.coley.recaf.info.member.FieldMember; +import software.coley.recaf.info.member.LocalVariable; import software.coley.recaf.info.member.MethodMember; import software.coley.recaf.services.inheritance.InheritanceGraph; import software.coley.recaf.services.mapping.IntermediateMappings; import software.coley.recaf.services.mapping.Mappings; import software.coley.recaf.services.mapping.data.MethodMapping; -import software.coley.recaf.services.mapping.gen.filter.*; +import software.coley.recaf.services.mapping.gen.filter.ExcludeClassesFilter; +import software.coley.recaf.services.mapping.gen.filter.ExcludeModifiersNameFilter; +import software.coley.recaf.services.mapping.gen.filter.IncludeClassesFilter; +import software.coley.recaf.services.mapping.gen.filter.IncludeModifiersNameFilter; +import software.coley.recaf.services.mapping.gen.filter.IncludeNameFilter; +import software.coley.recaf.services.mapping.gen.filter.NameGeneratorFilter; import software.coley.recaf.services.mapping.gen.naming.NameGenerator; import software.coley.recaf.services.search.match.StringPredicate; import software.coley.recaf.services.search.match.StringPredicateProvider; import software.coley.recaf.test.TestBase; import software.coley.recaf.test.TestClassUtils; -import software.coley.recaf.test.dummy.*; +import software.coley.recaf.test.dummy.AccessibleFields; +import software.coley.recaf.test.dummy.AccessibleMethods; +import software.coley.recaf.test.dummy.AccessibleMethodsChild; +import software.coley.recaf.test.dummy.StringConsumer; +import software.coley.recaf.test.dummy.StringConsumerUser; import software.coley.recaf.util.StringUtil; import software.coley.recaf.workspace.model.Workspace; import software.coley.recaf.workspace.model.resource.WorkspaceResource; @@ -59,6 +69,12 @@ public String mapField(@Nonnull ClassInfo owner, @Nonnull FieldMember field) { public String mapMethod(@Nonnull ClassInfo owner, @Nonnull MethodMember method) { return "mapped" + StringUtil.uppercaseFirstChar(method.getName()); } + + @Nonnull + @Override + public String mapVariable(@Nonnull ClassInfo owner, @Nonnull MethodMember declaringMethod, @Nonnull LocalVariable variable) { + return "mapped" + StringUtil.uppercaseFirstChar(variable.getName()); + } }; workspace = TestClassUtils.fromBundle(TestClassUtils.fromClasses( AccessibleFields.class, @@ -93,6 +109,8 @@ void testGeneral() { assertNotNull(mappings.getMappedFieldName(className, "protectedField", "I")); assertNotNull(mappings.getMappedFieldName(className, "publicField", "I")); assertNotNull(mappings.getMappedFieldName(className, "packageField", "I")); + className = StringConsumerUser.class.getName().replace('.', '/'); + assertNotNull(mappings.getMappedVariableName(className, "main", "([Ljava/lang/String;)V", "args", "[Ljava/lang/String;", 0)); } @Nested @@ -120,8 +138,20 @@ void testDefaultMapAll() { else if (inheritanceGraph.getVertex(info.getName()) .isLibraryMethod(method.getName(), method.getDescriptor())) assertNull(mappedMethodName, "Library method had generated mappings"); - else + else { assertNotNull(mappedMethodName, "Method not mapped: " + info.getName() + "." + method.getName()); + for (LocalVariable variable : method.getLocalVariables()) { + // Skip asserts for "this" local variable, we want to keep that one. + if (variable.getIndex() == 0 && variable.getName().equals("this")) + continue; + + String mappedVariableName = mappings.getMappedVariableName(info.getName(), + method.getName(), method.getDescriptor(), variable.getName(), + variable.getDescriptor(), variable.getIndex()); + assertNotNull(mappedMethodName, "Method variable not mapped: " + info.getName() + "." + + method.getName() + " - " + variable.getName()); + } + } } } } @@ -140,6 +170,7 @@ void testDefaultMapNothing() { assertEquals(0, intermediate.getClasses().size()); assertEquals(0, intermediate.getFields().size()); assertEquals(0, intermediate.getMethods().size()); + assertEquals(0, intermediate.getVariables().size()); } @Test @@ -220,6 +251,9 @@ void testExcludeModifiersOnAll() { assertEquals(0, intermediate.getClasses().size()); assertEquals(1, intermediate.getFields().size()); assertEquals(2, intermediate.getMethods().size()); + + // None of the methods included for mapping have any variables + assertTrue(intermediate.getVariables().isEmpty()); } @Test @@ -260,7 +294,7 @@ void testIncludeModifiers() { void testIncludeNameFilter() { StringPredicate predicate = strMatchProvider.newContainsPredicate("AccessibleMethods"); IncludeNameFilter filter = - new IncludeNameFilter(null, predicate, predicate, predicate); + new IncludeNameFilter(null, predicate, predicate, predicate, predicate); // Generate mappings Mappings mappings = mappingGenerator.generate(workspace, resource, inheritanceGraph, nameGenerator, filter); @@ -270,6 +304,7 @@ void testIncludeNameFilter() { assertEquals(2, intermediate.getClasses().size()); assertEquals(0, intermediate.getFields().size()); assertEquals(0, intermediate.getMethods().size()); + assertEquals(0, intermediate.getVariables().size()); } /** Similar to {@link #testIncludeNameFilter()} but with a filter that includes members when a class is mapped */ @@ -287,6 +322,7 @@ void testIncludeClassesFilter() { assertEquals(2, intermediate.getClasses().size()); assertEquals(0, intermediate.getFields().size()); assertEquals(2, intermediate.getMethods().size()); + assertEquals(0, intermediate.getVariables().size()); // None of the methods have locals String key = AccessibleMethods.class.getName().replace('.', '/'); String keyChild = AccessibleMethodsChild.class.getName().replace('.', '/'); List methodMappings = intermediate.getMethods().get(key); diff --git a/recaf-ui/src/main/java/software/coley/recaf/ui/pane/MappingGeneratorPane.java b/recaf-ui/src/main/java/software/coley/recaf/ui/pane/MappingGeneratorPane.java index e510bcc2a..80419966c 100644 --- a/recaf-ui/src/main/java/software/coley/recaf/ui/pane/MappingGeneratorPane.java +++ b/recaf-ui/src/main/java/software/coley/recaf/ui/pane/MappingGeneratorPane.java @@ -260,12 +260,14 @@ private Node createPreviewDisplay(@Nonnull Instance searchBarProvider int classes = mappings.getClasses().size(); int fields = mappings.getFields().size(); int methods = mappings.getMethods().size(); + int variables = mappings.getVariables().size(); String formatted = """ Mappings will rename: - %d classes - %d fields - %d methods - """.formatted(classes, fields, methods); + - %d variables + """.formatted(classes, fields, methods, variables); stats.setText(formatted); // Also update editor preview @@ -440,9 +442,11 @@ public class ExcludeName extends FilterWithConfigNode { private final StringProperty classPredicateId = new SimpleStringProperty(); private final StringProperty fieldPredicateId = new SimpleStringProperty(); private final StringProperty methodPredicateId = new SimpleStringProperty(); + private final StringProperty variablePredicateId = new SimpleStringProperty(); private final StringProperty className = new SimpleStringProperty("com/example/Foo"); private final StringProperty fieldName = new SimpleStringProperty("foo"); private final StringProperty methodName = new SimpleStringProperty("getFoo"); + private final StringProperty variableName = new SimpleStringProperty("fizz"); @Nonnull @Override @@ -477,7 +481,18 @@ else if (StringPredicateProvider.KEY_NOTHING.equals(key)) else return methodName; })); - return Lang.getBinding("misc.ignored"); + return variablePredicateId.isNotNull().flatMap(hasVariable -> { + if (hasVariable) + return Bindings.concat(Lang.getBinding("mapgen.filter.excludename"), ": ", variablePredicateId.flatMap(key -> { + if (StringPredicateProvider.KEY_ANYTHING.equals(key)) + return Lang.getBinding("string.match.anything"); + else if (StringPredicateProvider.KEY_NOTHING.equals(key)) + return Lang.getBinding("string.match.zilch"); + else + return variableName; + })); + return Lang.getBinding("misc.ignored"); + }); }); }); }); @@ -489,7 +504,8 @@ protected Function makeProvider() { return next -> new ExcludeNameFilter(next, classPredicateId.isNotNull().get() ? stringPredicateProvider.newBiStringPredicate(classPredicateId.get(), className.get()) : null, fieldPredicateId.isNotNull().get() ? stringPredicateProvider.newBiStringPredicate(fieldPredicateId.get(), fieldName.get()) : null, - methodPredicateId.isNotNull().get() ? stringPredicateProvider.newBiStringPredicate(methodPredicateId.get(), methodName.get()) : null + methodPredicateId.isNotNull().get() ? stringPredicateProvider.newBiStringPredicate(methodPredicateId.get(), methodName.get()) : null, + variablePredicateId.isNotNull().get() ? stringPredicateProvider.newBiStringPredicate(variablePredicateId.get(), variableName.get()) : null ); } @@ -498,6 +514,7 @@ protected void fillConfigurator(@Nonnull BiConsumer sink) { BoundTextField txtClass = new BoundTextField(className); BoundTextField txtField = new BoundTextField(fieldName); BoundTextField txtMethod = new BoundTextField(methodName); + BoundTextField txtVariable = new BoundTextField(variableName); txtClass.disableProperty().bind(classPredicateId.isNull() .or(classPredicateId.isEqualTo(StringPredicateProvider.KEY_ANYTHING)) .or(classPredicateId.isEqualTo(StringPredicateProvider.KEY_NOTHING))); @@ -507,6 +524,9 @@ protected void fillConfigurator(@Nonnull BiConsumer sink) { txtMethod.disableProperty().bind(methodPredicateId.isNull() .or(methodPredicateId.isEqualTo(StringPredicateProvider.KEY_ANYTHING)) .or(methodPredicateId.isEqualTo(StringPredicateProvider.KEY_NOTHING))); + txtVariable.disableProperty().bind(variablePredicateId.isNull() + .or(variablePredicateId.isEqualTo(StringPredicateProvider.KEY_ANYTHING)) + .or(variablePredicateId.isEqualTo(StringPredicateProvider.KEY_NOTHING))); GridPane grid = new GridPane(); grid.setVgap(5); @@ -517,6 +537,8 @@ protected void fillConfigurator(@Nonnull BiConsumer sink) { txtField, new BoundComboBox<>(fieldPredicateId, stringPredicatesWithNull, textPredicateConverter)); grid.addRow(2, new BoundLabel(Lang.getBinding("mapgen.filter.method-name")), txtMethod, new BoundComboBox<>(methodPredicateId, stringPredicatesWithNull, textPredicateConverter)); + grid.addRow(3, new BoundLabel(Lang.getBinding("mapgen.filter.variable-name")), + txtMethod, new BoundComboBox<>(variablePredicateId, stringPredicatesWithNull, textPredicateConverter)); sink.accept(null, grid); } } @@ -630,9 +652,11 @@ public class IncludeName extends FilterWithConfigNode { private final StringProperty classPredicateId = new SimpleStringProperty(); private final StringProperty fieldPredicateId = new SimpleStringProperty(); private final StringProperty methodPredicateId = new SimpleStringProperty(); + private final StringProperty variablePredicateId = new SimpleStringProperty(); private final StringProperty className = new SimpleStringProperty("com/example/Foo"); private final StringProperty fieldName = new SimpleStringProperty("foo"); private final StringProperty methodName = new SimpleStringProperty("getFoo"); + private final StringProperty variableName = new SimpleStringProperty("fizz"); @Nonnull @Override @@ -667,7 +691,18 @@ else if (StringPredicateProvider.KEY_NOTHING.equals(key)) else return methodName; })); - return Lang.getBinding("misc.ignored"); + return variablePredicateId.isNotNull().flatMap(hasVariable -> { + if (hasVariable) + return Bindings.concat(Lang.getBinding("mapgen.filter.includename"), ": ", variablePredicateId.flatMap(key -> { + if (StringPredicateProvider.KEY_ANYTHING.equals(key)) + return Lang.getBinding("string.match.anything"); + else if (StringPredicateProvider.KEY_NOTHING.equals(key)) + return Lang.getBinding("string.match.zilch"); + else + return variableName; + })); + return Lang.getBinding("misc.ignored"); + }); }); }); }); @@ -679,7 +714,8 @@ protected Function makeProvider() { return next -> new IncludeNameFilter(next, classPredicateId.isNotNull().get() ? stringPredicateProvider.newBiStringPredicate(classPredicateId.get(), className.get()) : null, fieldPredicateId.isNotNull().get() ? stringPredicateProvider.newBiStringPredicate(fieldPredicateId.get(), fieldName.get()) : null, - methodPredicateId.isNotNull().get() ? stringPredicateProvider.newBiStringPredicate(methodPredicateId.get(), methodName.get()) : null + methodPredicateId.isNotNull().get() ? stringPredicateProvider.newBiStringPredicate(methodPredicateId.get(), methodName.get()) : null, + variablePredicateId.isNotNull().get() ? stringPredicateProvider.newBiStringPredicate(variablePredicateId.get(), variableName.get()) : null ); } @@ -688,6 +724,7 @@ protected void fillConfigurator(@Nonnull BiConsumer sink) { BoundTextField txtClass = new BoundTextField(className); BoundTextField txtField = new BoundTextField(fieldName); BoundTextField txtMethod = new BoundTextField(methodName); + BoundTextField txtVariable = new BoundTextField(variableName); txtClass.disableProperty().bind(classPredicateId.isNull() .or(classPredicateId.isEqualTo(StringPredicateProvider.KEY_ANYTHING)) .or(classPredicateId.isEqualTo(StringPredicateProvider.KEY_NOTHING))); @@ -697,6 +734,10 @@ protected void fillConfigurator(@Nonnull BiConsumer sink) { txtMethod.disableProperty().bind(methodPredicateId.isNull() .or(methodPredicateId.isEqualTo(StringPredicateProvider.KEY_ANYTHING)) .or(methodPredicateId.isEqualTo(StringPredicateProvider.KEY_NOTHING))); + txtVariable.disableProperty().bind(variablePredicateId.isNull() + .or(variablePredicateId.isEqualTo(StringPredicateProvider.KEY_ANYTHING)) + .or(variablePredicateId.isEqualTo(StringPredicateProvider.KEY_NOTHING))); + GridPane grid = new GridPane(); grid.setVgap(5); grid.setHgap(5); @@ -706,6 +747,8 @@ protected void fillConfigurator(@Nonnull BiConsumer sink) { txtField, new BoundComboBox<>(fieldPredicateId, stringPredicatesWithNull, textPredicateConverter)); grid.addRow(2, new BoundLabel(Lang.getBinding("mapgen.filter.method-name")), txtMethod, new BoundComboBox<>(methodPredicateId, stringPredicatesWithNull, textPredicateConverter)); + grid.addRow(3, new BoundLabel(Lang.getBinding("mapgen.filter.variable-name")), + txtMethod, new BoundComboBox<>(variablePredicateId, stringPredicatesWithNull, textPredicateConverter)); sink.accept(null, grid); } } @@ -793,6 +836,7 @@ public static class IncludeLongName extends FilterWithConfigNode display() { @Nonnull @Override protected Function makeProvider() { - return next -> new IncludeLongNameFilter(next, length.get(), classes.get(), fields.get(), methods.get()); + return next -> new IncludeLongNameFilter(next, length.get(), classes.get(), fields.get(), methods.get(), variables.get()); } @Override @@ -812,6 +856,7 @@ protected void fillConfigurator(@Nonnull BiConsumer sink) { sink.accept(null, new BoundCheckBox(Lang.getBinding("mapgen.filter.includeclass"), classes)); sink.accept(null, new BoundCheckBox(Lang.getBinding("mapgen.filter.includefield"), fields)); sink.accept(null, new BoundCheckBox(Lang.getBinding("mapgen.filter.includemethod"), methods)); + sink.accept(null, new BoundCheckBox(Lang.getBinding("mapgen.filter.includevariable"), variables)); } } diff --git a/recaf-ui/src/main/resources/translations/en_US.lang b/recaf-ui/src/main/resources/translations/en_US.lang index e37ea2d81..4ce7e4936 100644 --- a/recaf-ui/src/main/resources/translations/en_US.lang +++ b/recaf-ui/src/main/resources/translations/en_US.lang @@ -435,6 +435,7 @@ mapgen.filter.class-name=Class name mapgen.filter.owner-name=Owner name mapgen.filter.field-name=Field name mapgen.filter.method-name=Method name +mapgen.filter.variable-name=Variable name mapgen.filters=Filters mapgen.filters.add=Add filter mapgen.filters.edit=Edit selected @@ -453,6 +454,7 @@ mapgen.filter.includemodifier=Include modifiers mapgen.filter.includeclass=Include on classes mapgen.filter.includefield=Include on fields mapgen.filter.includemethod=Include on methods +mapgen.filter.includevariable=Include on variables mapgen.filter.includewhitespacenames=Include whitespaces mapgen.filter.includenonasciinames=Include non-ascii mapgen.filter.includekeywords=Include keywords