From 4dd1120ab24381319007c0bdd1929e1ed7b52166 Mon Sep 17 00:00:00 2001 From: Jake Potrebic Date: Thu, 26 Sep 2024 13:45:03 -0700 Subject: [PATCH 1/4] initial work on supporting better versioning --- .../method/params/DirectParameterRewrite.java | 25 +++++++++++ .../io/papermc/asm/versioned/ApiVersion.java | 23 ++++++++++ .../asm/versioned/VersionedRuleFactory.java | 43 +++++++++++++++++++ .../VersionedRuleFactoryBuilder.java | 34 +++++++++++++++ .../VersionedRuleFactoryBuilderImpl.java | 34 +++++++++++++++ .../VersionedMethodMatcherBuilder.java | 4 ++ ...VersionedTargetedMethodMatcherBuilder.java | 19 ++++++++ ...ionedTargetedMethodMatcherBuilderImpl.java | 28 ++++++++++++ 8 files changed, 210 insertions(+) create mode 100644 src/main/java/io/papermc/asm/versioned/ApiVersion.java create mode 100644 src/main/java/io/papermc/asm/versioned/VersionedRuleFactory.java create mode 100644 src/main/java/io/papermc/asm/versioned/VersionedRuleFactoryBuilder.java create mode 100644 src/main/java/io/papermc/asm/versioned/VersionedRuleFactoryBuilderImpl.java create mode 100644 src/main/java/io/papermc/asm/versioned/builder/VersionedMethodMatcherBuilder.java create mode 100644 src/main/java/io/papermc/asm/versioned/builder/VersionedTargetedMethodMatcherBuilder.java create mode 100644 src/main/java/io/papermc/asm/versioned/builder/VersionedTargetedMethodMatcherBuilderImpl.java diff --git a/src/main/java/io/papermc/asm/rules/method/params/DirectParameterRewrite.java b/src/main/java/io/papermc/asm/rules/method/params/DirectParameterRewrite.java index 678dc32..ca075d4 100644 --- a/src/main/java/io/papermc/asm/rules/method/params/DirectParameterRewrite.java +++ b/src/main/java/io/papermc/asm/rules/method/params/DirectParameterRewrite.java @@ -1,10 +1,18 @@ package io.papermc.asm.rules.method.params; +import io.papermc.asm.versioned.ApiVersion; +import io.papermc.asm.versioned.VersionedRuleFactory; +import io.papermc.asm.rules.RewriteRule; import io.papermc.asm.rules.builder.matcher.method.targeted.TargetedMethodMatcher; import io.papermc.asm.rules.method.generated.TargetedTypeGeneratedStaticRewrite; import java.lang.constant.ClassDesc; import java.lang.reflect.Method; +import java.util.Collections; +import java.util.Map; +import java.util.NavigableMap; import java.util.Set; +import java.util.TreeMap; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Rewrites matching bytecode to a generated method which will invoke the static handler on all parameters that need to be converted. That @@ -16,4 +24,21 @@ * @param staticHandler the method which will be used to convert the legacy type to the new type */ public record DirectParameterRewrite(Set owners, ClassDesc existingType, TargetedMethodMatcher methodMatcher, Method staticHandler) implements TargetedTypeGeneratedStaticRewrite.Parameter { + + public record Versioned(Set owners, ClassDesc existingType, NavigableMap> versions) implements VersionedRuleFactory { + + public Versioned { + versions = Collections.unmodifiableNavigableMap(versions); + } + + @Override + public @Nullable RewriteRule createRule(final ApiVersion apiVersion) { + final Map.@Nullable Entry> apiVersionEntryEntry = this.versions.ceilingEntry(apiVersion); + if (apiVersionEntryEntry == null) { + return null; + } + final Map.Entry entry = apiVersionEntryEntry.getValue(); + return new DirectParameterRewrite(this.owners, this.existingType, entry.getKey(), entry.getValue()); + } + } } diff --git a/src/main/java/io/papermc/asm/versioned/ApiVersion.java b/src/main/java/io/papermc/asm/versioned/ApiVersion.java new file mode 100644 index 0000000..439b25b --- /dev/null +++ b/src/main/java/io/papermc/asm/versioned/ApiVersion.java @@ -0,0 +1,23 @@ +package io.papermc.asm.versioned; + +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.OverrideOnly +public interface ApiVersion extends Comparable { + + default boolean isNewerThan(final ApiVersion apiVersion) { + return this.compareTo(apiVersion) > 0; + } + + default boolean isOlderThan(final ApiVersion apiVersion) { + return this.compareTo(apiVersion) < 0; + } + + default boolean isNewerThanOrSameAs(final ApiVersion apiVersion) { + return this.compareTo(apiVersion) >= 0; + } + + default boolean isOlderThanOrSameAs(final ApiVersion apiVersion) { + return this.compareTo(apiVersion) <= 0; + } +} diff --git a/src/main/java/io/papermc/asm/versioned/VersionedRuleFactory.java b/src/main/java/io/papermc/asm/versioned/VersionedRuleFactory.java new file mode 100644 index 0000000..bfcc524 --- /dev/null +++ b/src/main/java/io/papermc/asm/versioned/VersionedRuleFactory.java @@ -0,0 +1,43 @@ +package io.papermc.asm.versioned; + +import io.papermc.asm.rules.RewriteRule; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.qual.DefaultQualifier; + +@DefaultQualifier(NonNull.class) +public interface VersionedRuleFactory { + + static VersionedRuleFactory chain(final VersionedRuleFactory... factories) { + return chain(Arrays.asList(factories)); + } + + static VersionedRuleFactory chain(final Collection factories) { + return new Chain(List.copyOf(factories)); + } + + @Nullable RewriteRule createRule(ApiVersion apiVersion); + + record Chain(List factories) implements VersionedRuleFactory{ + + public Chain { + factories = List.copyOf(factories); + } + + @Override + public RewriteRule createRule(final ApiVersion apiVersion) { + final List rules = new ArrayList<>(); + for (final VersionedRuleFactory factory : this.factories) { + final @Nullable RewriteRule rule = factory.createRule(apiVersion); + if (rule != null) { + rules.add(rule); + } + } + return RewriteRule.chain(rules); + } + } +} diff --git a/src/main/java/io/papermc/asm/versioned/VersionedRuleFactoryBuilder.java b/src/main/java/io/papermc/asm/versioned/VersionedRuleFactoryBuilder.java new file mode 100644 index 0000000..d664cfb --- /dev/null +++ b/src/main/java/io/papermc/asm/versioned/VersionedRuleFactoryBuilder.java @@ -0,0 +1,34 @@ +package io.papermc.asm.versioned; + +import io.papermc.asm.rules.builder.matcher.method.targeted.TargetedMethodMatcher; +import java.lang.constant.ClassDesc; +import java.lang.reflect.Method; +import java.util.Map; +import java.util.NavigableMap; +import java.util.Set; +import java.util.TreeMap; + +import static io.papermc.asm.util.DescriptorUtils.desc; + +public interface VersionedRuleFactoryBuilder { + + static VersionedRuleFactoryBuilder create(final Set owners) { + return new VersionedRuleFactoryBuilderImpl(owners); + } + + default void changeParamDirect(final Class newParamType, final ApiVersion apiVersion, final TargetedMethodMatcher methodMatcher, final Method staticHandler) { + this.changeParamDirect(desc(newParamType), apiVersion, methodMatcher, staticHandler); + } + + default void changeParamDirect(final ClassDesc newParamType, final ApiVersion apiVersion, final TargetedMethodMatcher methodMatcher, final Method staticHandler) { + this.changeParamDirect(newParamType, new TreeMap<>(Map.of(apiVersion, Map.entry(methodMatcher, staticHandler)))); + } + + default void changeParamDirect(final Class newParamType, final NavigableMap> versions) { + this.changeParamDirect(desc(newParamType), versions); + } + + void changeParamDirect(ClassDesc newParamType, NavigableMap> versions); + + VersionedRuleFactory build(); +} diff --git a/src/main/java/io/papermc/asm/versioned/VersionedRuleFactoryBuilderImpl.java b/src/main/java/io/papermc/asm/versioned/VersionedRuleFactoryBuilderImpl.java new file mode 100644 index 0000000..b7b7455 --- /dev/null +++ b/src/main/java/io/papermc/asm/versioned/VersionedRuleFactoryBuilderImpl.java @@ -0,0 +1,34 @@ +package io.papermc.asm.versioned; + +import io.papermc.asm.rules.builder.matcher.method.targeted.TargetedMethodMatcher; +import io.papermc.asm.rules.method.params.DirectParameterRewrite; +import java.lang.constant.ClassDesc; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.NavigableMap; +import java.util.Set; + +public class VersionedRuleFactoryBuilderImpl implements VersionedRuleFactoryBuilder { + + final Set owners; + private final List factories = new ArrayList<>(); + + public VersionedRuleFactoryBuilderImpl(final Set owners) { + this.owners = Set.copyOf(owners); + } + + @Override + public void changeParamDirect(final ClassDesc newParamType, final NavigableMap> versions) { + this.factories.add(new DirectParameterRewrite.Versioned(this.owners, newParamType, versions)); + } + + @Override + public VersionedRuleFactory build() { + if (this.factories.size() == 1) { + return this.factories.get(0); + } + return VersionedRuleFactory.chain(this.factories); + } +} diff --git a/src/main/java/io/papermc/asm/versioned/builder/VersionedMethodMatcherBuilder.java b/src/main/java/io/papermc/asm/versioned/builder/VersionedMethodMatcherBuilder.java new file mode 100644 index 0000000..0028a69 --- /dev/null +++ b/src/main/java/io/papermc/asm/versioned/builder/VersionedMethodMatcherBuilder.java @@ -0,0 +1,4 @@ +package io.papermc.asm.versioned.builder; + +public interface VersionedMethodMatcherBuilder { +} diff --git a/src/main/java/io/papermc/asm/versioned/builder/VersionedTargetedMethodMatcherBuilder.java b/src/main/java/io/papermc/asm/versioned/builder/VersionedTargetedMethodMatcherBuilder.java new file mode 100644 index 0000000..21771d9 --- /dev/null +++ b/src/main/java/io/papermc/asm/versioned/builder/VersionedTargetedMethodMatcherBuilder.java @@ -0,0 +1,19 @@ +package io.papermc.asm.versioned.builder; + +import io.papermc.asm.rules.builder.matcher.method.targeted.TargetedMethodMatcher; +import io.papermc.asm.util.Builder; +import io.papermc.asm.versioned.ApiVersion; +import java.lang.reflect.Method; +import java.util.Map; +import java.util.NavigableMap; +import org.jetbrains.annotations.Contract; + +public interface VersionedTargetedMethodMatcherBuilder extends Builder>> { + + static VersionedTargetedMethodMatcherBuilder builder() { + return new VersionedTargetedMethodMatcherBuilderImpl(); + } + + @Contract(value = "_, _, _ -> this", mutates = "this") + VersionedTargetedMethodMatcherBuilder with(ApiVersion apiVersion, TargetedMethodMatcher targetedMethodMatcher, Method method); +} diff --git a/src/main/java/io/papermc/asm/versioned/builder/VersionedTargetedMethodMatcherBuilderImpl.java b/src/main/java/io/papermc/asm/versioned/builder/VersionedTargetedMethodMatcherBuilderImpl.java new file mode 100644 index 0000000..04f39df --- /dev/null +++ b/src/main/java/io/papermc/asm/versioned/builder/VersionedTargetedMethodMatcherBuilderImpl.java @@ -0,0 +1,28 @@ +package io.papermc.asm.versioned.builder; + +import io.papermc.asm.rules.builder.matcher.method.targeted.TargetedMethodMatcher; +import io.papermc.asm.versioned.ApiVersion; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.Map; +import java.util.NavigableMap; +import java.util.TreeMap; + +public class VersionedTargetedMethodMatcherBuilderImpl implements VersionedTargetedMethodMatcherBuilder { + + private final NavigableMap> versions = new TreeMap<>(); + + @Override + public VersionedTargetedMethodMatcherBuilder with(final ApiVersion apiVersion, final TargetedMethodMatcher targetedMethodMatcher, final Method method) { + if (this.versions.containsKey(apiVersion)) { + throw new IllegalArgumentException("Duplicate version: " + apiVersion); + } + this.versions.put(apiVersion, Map.entry(targetedMethodMatcher, method)); + return this; + } + + @Override + public NavigableMap> build() { + return Collections.unmodifiableNavigableMap(this.versions); + } +} From 159ba644dcc951cba1312a2fdf78c90c66dfa67a Mon Sep 17 00:00:00 2001 From: Jake Potrebic Date: Thu, 26 Sep 2024 21:30:28 -0700 Subject: [PATCH 2/4] work on enum renames --- .../io/papermc/asm/rules/RewriteRule.java | 24 ++-- .../asm/rules/builder/RuleFactoryImpl.java | 3 - .../asm/rules/method/MethodRewriteRule.java | 14 +- .../method/params/DirectParameterRewrite.java | 25 +--- .../method/params/FuzzyParameterRewrite.java | 12 ++ .../method/params/SuperTypeParamRewrite.java | 12 ++ .../method/returns/SubTypeReturnRewrite.java | 12 ++ .../asm/rules/rename/EnumRenameBuilder.java | 44 ++++++ .../papermc/asm/rules/rename/EnumRenamer.java | 8 ++ .../rules/rename/EnumValueOfRewriteRule.java | 136 ++++++++++++++++++ .../papermc/asm/rules/rename/RenameRule.java | 130 +++-------------- .../asm/rules/rename/RenameRuleBuilder.java | 116 +++++++++++++++ .../{ => asm}/FixedAnnotationRemapper.java | 2 +- .../rename/{ => asm}/FixedClassRemapper.java | 2 +- .../rename/{ => asm}/FixedFieldRemapper.java | 2 +- .../rename/{ => asm}/FixedMethodRemapper.java | 2 +- .../FixedRecordComponentRemapper.java | 2 +- .../CachingVersionedRuleFactory.java | 28 ++++ .../asm/versioned/VersionedRuleFactory.java | 47 +++++- .../VersionedRuleFactoryBuilder.java | 70 +++++++-- .../VersionedRuleFactoryBuilderImpl.java | 29 ++-- .../VersionedMethodMatcherBuilder.java | 4 - .../matcher/VersionedMatcherBase.java | 25 ++++ .../matcher/VersionedMethodMatcher.java | 26 ++++ .../VersionedMethodMatcherBuilder.java | 13 ++ .../VersionedMethodMatcherBuilderImpl.java | 26 ++++ .../VersionedTargetedMethodMatcher.java | 27 ++++ ...VersionedTargetedMethodMatcherBuilder.java | 12 +- ...ionedTargetedMethodMatcherBuilderImpl.java | 14 +- .../papermc/asm/versioned/package-info.java | 5 + src/test/java/io/papermc/asm/ApiVersion.java | 48 +++++++ src/test/java/io/papermc/asm/TestUtil.java | 4 +- .../asm/rules/methods/MethodRewritesTest.java | 49 +++++++ .../asm/rules/rename/RenameRuleTest.java | 12 +- .../asm/versioned/VersionedTester.java | 46 ++++++ src/testData/java/data/rename/RenameTest.java | 7 + .../java/data/types/rename/TestEnum.java | 2 + .../expected/data/rename/RenameTest.class | Bin 2026 -> 2869 bytes .../data/types/rename/RenamedTestEnum.java | 2 + 39 files changed, 845 insertions(+), 197 deletions(-) create mode 100644 src/main/java/io/papermc/asm/rules/rename/EnumRenameBuilder.java create mode 100644 src/main/java/io/papermc/asm/rules/rename/EnumRenamer.java create mode 100644 src/main/java/io/papermc/asm/rules/rename/EnumValueOfRewriteRule.java create mode 100644 src/main/java/io/papermc/asm/rules/rename/RenameRuleBuilder.java rename src/main/java/io/papermc/asm/rules/rename/{ => asm}/FixedAnnotationRemapper.java (96%) rename src/main/java/io/papermc/asm/rules/rename/{ => asm}/FixedClassRemapper.java (97%) rename src/main/java/io/papermc/asm/rules/rename/{ => asm}/FixedFieldRemapper.java (93%) rename src/main/java/io/papermc/asm/rules/rename/{ => asm}/FixedMethodRemapper.java (94%) rename src/main/java/io/papermc/asm/rules/rename/{ => asm}/FixedRecordComponentRemapper.java (94%) create mode 100644 src/main/java/io/papermc/asm/versioned/CachingVersionedRuleFactory.java delete mode 100644 src/main/java/io/papermc/asm/versioned/builder/VersionedMethodMatcherBuilder.java create mode 100644 src/main/java/io/papermc/asm/versioned/matcher/VersionedMatcherBase.java create mode 100644 src/main/java/io/papermc/asm/versioned/matcher/VersionedMethodMatcher.java create mode 100644 src/main/java/io/papermc/asm/versioned/matcher/VersionedMethodMatcherBuilder.java create mode 100644 src/main/java/io/papermc/asm/versioned/matcher/VersionedMethodMatcherBuilderImpl.java create mode 100644 src/main/java/io/papermc/asm/versioned/matcher/targeted/VersionedTargetedMethodMatcher.java rename src/main/java/io/papermc/asm/versioned/{builder => matcher/targeted}/VersionedTargetedMethodMatcherBuilder.java (53%) rename src/main/java/io/papermc/asm/versioned/{builder => matcher/targeted}/VersionedTargetedMethodMatcherBuilderImpl.java (52%) create mode 100644 src/main/java/io/papermc/asm/versioned/package-info.java create mode 100644 src/test/java/io/papermc/asm/ApiVersion.java create mode 100644 src/test/java/io/papermc/asm/versioned/VersionedTester.java diff --git a/src/main/java/io/papermc/asm/rules/RewriteRule.java b/src/main/java/io/papermc/asm/rules/RewriteRule.java index 3216b00..c1fe310 100644 --- a/src/main/java/io/papermc/asm/rules/RewriteRule.java +++ b/src/main/java/io/papermc/asm/rules/RewriteRule.java @@ -2,6 +2,7 @@ import io.papermc.asm.ClassProcessingContext; import io.papermc.asm.rules.builder.RuleFactory; +import io.papermc.asm.util.DescriptorUtils; import java.lang.constant.ClassDesc; import java.util.ArrayList; import java.util.Arrays; @@ -26,7 +27,7 @@ static RewriteRule forOwnerClass(final Class owner, final Consumer> owners, final Consumer firstFactoryConsumer, final Consumer... factoryConsumers) { - return forOwners(owners.stream().map(c -> c.describeConstable().orElseThrow()).collect(Collectors.toUnmodifiableSet()), firstFactoryConsumer, factoryConsumers); + return forOwners(owners.stream().map(DescriptorUtils::desc).collect(Collectors.toUnmodifiableSet()), firstFactoryConsumer, factoryConsumers); } @SafeVarargs @@ -49,7 +50,13 @@ static RewriteRule chain(final RewriteRule... rules) { } static RewriteRule chain(final Collection rules) { - return new Chain(List.copyOf(rules)); + final List filteredRules = rules.stream().filter(r -> r != EMPTY).toList(); + if (filteredRules.isEmpty()) { + return EMPTY; + } else if (filteredRules.size() == 1) { + return filteredRules.iterator().next(); + } + return new Chain(filteredRules); } static ChainBuilder chain() { @@ -73,16 +80,17 @@ default ClassVisitor createVisitor(final int api, final ClassVisitor parent, fin } } - record Chain(List rules) implements RewriteRule { - public Chain(final List rules) { - this.rules = List.copyOf(rules); + record Chain(List rules) implements RewriteRule { + public Chain { + rules = List.copyOf(rules); } @Override public ClassVisitor createVisitor(final int api, final ClassVisitor parent, final ClassProcessingContext context) { ClassVisitor visitor = parent; - for (final RewriteRule rule : this.rules) { - visitor = rule.createVisitor(api, visitor, context); + // iterate over this.rules backwards to ensure the first rule is the outermost visitor + for (int i = this.rules.size() - 1; i >= 0; i--) { + visitor = this.rules.get(i).createVisitor(api, visitor, context); } return visitor; } @@ -109,7 +117,7 @@ public ChainBuilder then(final RewriteRule... rules) { } public RewriteRule build() { - return new Chain(this.rules); + return RewriteRule.chain(this.rules); } } } diff --git a/src/main/java/io/papermc/asm/rules/builder/RuleFactoryImpl.java b/src/main/java/io/papermc/asm/rules/builder/RuleFactoryImpl.java index 55f1871..af32a9c 100644 --- a/src/main/java/io/papermc/asm/rules/builder/RuleFactoryImpl.java +++ b/src/main/java/io/papermc/asm/rules/builder/RuleFactoryImpl.java @@ -101,9 +101,6 @@ private static Method isStatic(final Method staticHandler) { @Override public RewriteRule build() { - if (this.rules.size() == 1) { - return this.rules.get(0); - } return RewriteRule.chain(this.rules); } } diff --git a/src/main/java/io/papermc/asm/rules/method/MethodRewriteRule.java b/src/main/java/io/papermc/asm/rules/method/MethodRewriteRule.java index dc48c39..e6947f0 100644 --- a/src/main/java/io/papermc/asm/rules/method/MethodRewriteRule.java +++ b/src/main/java/io/papermc/asm/rules/method/MethodRewriteRule.java @@ -5,8 +5,9 @@ import io.papermc.asm.rules.method.rewrite.MethodRewrite; import java.lang.constant.ClassDesc; import java.lang.constant.MethodTypeDesc; -import java.util.LinkedHashMap; +import java.util.Comparator; import java.util.Map; +import java.util.TreeMap; import org.checkerframework.checker.nullness.qual.Nullable; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.Handle; @@ -56,9 +57,16 @@ default boolean shouldProcess(final ClassProcessingContext context, final int op @Override default ClassVisitor createVisitor(final int api, final ClassVisitor parent, final ClassProcessingContext context) { - record MethodKey(String owner, String name, MethodTypeDesc descriptor) { + record MethodKey(String owner, String name, MethodTypeDesc descriptor) implements Comparable { + private static final Comparator COMPARATOR = Comparator.comparing(MethodKey::owner) + .thenComparing(MethodKey::name) + .thenComparing(key -> key.descriptor().descriptorString()); + @Override + public int compareTo(final MethodKey o) { + return COMPARATOR.compare(this, o); + } } - final Map methodsToGenerate = new LinkedHashMap<>(); + final Map methodsToGenerate = new TreeMap<>(); return new ClassVisitor(api, parent) { @Override diff --git a/src/main/java/io/papermc/asm/rules/method/params/DirectParameterRewrite.java b/src/main/java/io/papermc/asm/rules/method/params/DirectParameterRewrite.java index ca075d4..7b4f75b 100644 --- a/src/main/java/io/papermc/asm/rules/method/params/DirectParameterRewrite.java +++ b/src/main/java/io/papermc/asm/rules/method/params/DirectParameterRewrite.java @@ -1,18 +1,14 @@ package io.papermc.asm.rules.method.params; -import io.papermc.asm.versioned.ApiVersion; -import io.papermc.asm.versioned.VersionedRuleFactory; import io.papermc.asm.rules.RewriteRule; import io.papermc.asm.rules.builder.matcher.method.targeted.TargetedMethodMatcher; import io.papermc.asm.rules.method.generated.TargetedTypeGeneratedStaticRewrite; +import io.papermc.asm.versioned.ApiVersion; +import io.papermc.asm.versioned.VersionedRuleFactory; +import io.papermc.asm.versioned.matcher.targeted.VersionedTargetedMethodMatcher; import java.lang.constant.ClassDesc; import java.lang.reflect.Method; -import java.util.Collections; -import java.util.Map; -import java.util.NavigableMap; import java.util.Set; -import java.util.TreeMap; -import org.checkerframework.checker.nullness.qual.Nullable; /** * Rewrites matching bytecode to a generated method which will invoke the static handler on all parameters that need to be converted. That @@ -25,20 +21,11 @@ */ public record DirectParameterRewrite(Set owners, ClassDesc existingType, TargetedMethodMatcher methodMatcher, Method staticHandler) implements TargetedTypeGeneratedStaticRewrite.Parameter { - public record Versioned(Set owners, ClassDesc existingType, NavigableMap> versions) implements VersionedRuleFactory { - - public Versioned { - versions = Collections.unmodifiableNavigableMap(versions); - } + public record Versioned(Set owners, ClassDesc existingType, VersionedTargetedMethodMatcher versions) implements VersionedRuleFactory { @Override - public @Nullable RewriteRule createRule(final ApiVersion apiVersion) { - final Map.@Nullable Entry> apiVersionEntryEntry = this.versions.ceilingEntry(apiVersion); - if (apiVersionEntryEntry == null) { - return null; - } - final Map.Entry entry = apiVersionEntryEntry.getValue(); - return new DirectParameterRewrite(this.owners, this.existingType, entry.getKey(), entry.getValue()); + public RewriteRule createRule(final ApiVersion apiVersion) { + return this.versions.ruleForVersion(apiVersion, pair -> new DirectParameterRewrite(this.owners, this.existingType, pair.matcher(), pair.staticHandler())); } } } diff --git a/src/main/java/io/papermc/asm/rules/method/params/FuzzyParameterRewrite.java b/src/main/java/io/papermc/asm/rules/method/params/FuzzyParameterRewrite.java index 5d7ed34..b6897ea 100644 --- a/src/main/java/io/papermc/asm/rules/method/params/FuzzyParameterRewrite.java +++ b/src/main/java/io/papermc/asm/rules/method/params/FuzzyParameterRewrite.java @@ -1,9 +1,13 @@ package io.papermc.asm.rules.method.params; import io.papermc.asm.ClassProcessingContext; +import io.papermc.asm.rules.RewriteRule; import io.papermc.asm.rules.builder.matcher.method.targeted.TargetedMethodMatcher; import io.papermc.asm.rules.method.generated.TargetedTypeGeneratedStaticRewrite; import io.papermc.asm.rules.method.rewrite.MethodRewrite; +import io.papermc.asm.versioned.ApiVersion; +import io.papermc.asm.versioned.VersionedRuleFactory; +import io.papermc.asm.versioned.matcher.targeted.VersionedTargetedMethodMatcher; import java.lang.constant.ClassDesc; import java.lang.constant.ConstantDescs; import java.lang.constant.MethodTypeDesc; @@ -53,4 +57,12 @@ public MethodRewrite createRewrite(final ClassProcessingContext arguments[MethodRewrite.DYNAMIC_TYPE_IDX] = Type.getMethodType(newDynamicMethodType.descriptorString()); }); } + + public record Versioned(Set owners, ClassDesc existingType, VersionedTargetedMethodMatcher versions) implements VersionedRuleFactory { + + @Override + public RewriteRule createRule(final ApiVersion apiVersion) { + return this.versions.ruleForVersion(apiVersion, pair -> new FuzzyParameterRewrite(this.owners, this.existingType, pair.matcher(), pair.staticHandler())); + } + } } diff --git a/src/main/java/io/papermc/asm/rules/method/params/SuperTypeParamRewrite.java b/src/main/java/io/papermc/asm/rules/method/params/SuperTypeParamRewrite.java index 414136d..d78154d 100644 --- a/src/main/java/io/papermc/asm/rules/method/params/SuperTypeParamRewrite.java +++ b/src/main/java/io/papermc/asm/rules/method/params/SuperTypeParamRewrite.java @@ -1,10 +1,14 @@ package io.papermc.asm.rules.method.params; import io.papermc.asm.ClassProcessingContext; +import io.papermc.asm.rules.RewriteRule; import io.papermc.asm.rules.builder.matcher.method.MethodMatcher; import io.papermc.asm.rules.method.OwnableMethodRewriteRule; import io.papermc.asm.rules.method.rewrite.MethodRewrite; import io.papermc.asm.rules.method.rewrite.SimpleRewrite; +import io.papermc.asm.versioned.ApiVersion; +import io.papermc.asm.versioned.VersionedRuleFactory; +import io.papermc.asm.versioned.matcher.VersionedMethodMatcher; import java.lang.constant.ClassDesc; import java.lang.constant.MethodTypeDesc; import java.util.Set; @@ -31,4 +35,12 @@ public MethodRewrite rewrite(final ClassProcessingContext context, final bool private MethodTypeDesc modifyMethodDescriptor(final MethodTypeDesc methodDescriptor) { return replaceParameters(methodDescriptor, isEqual(this.oldParamType()), this.newParamType()); } + + public record Versioned(Set owners, ClassDesc newParamType, VersionedMethodMatcher versions) implements VersionedRuleFactory { + + @Override + public RewriteRule createRule(final ApiVersion apiVersion) { + return this.versions.ruleForVersion(apiVersion, pair -> new SuperTypeParamRewrite(this.owners(), pair.matcher(), pair.legacyType(), this.newParamType())); + } + } } diff --git a/src/main/java/io/papermc/asm/rules/method/returns/SubTypeReturnRewrite.java b/src/main/java/io/papermc/asm/rules/method/returns/SubTypeReturnRewrite.java index c8acfd7..1cffd49 100644 --- a/src/main/java/io/papermc/asm/rules/method/returns/SubTypeReturnRewrite.java +++ b/src/main/java/io/papermc/asm/rules/method/returns/SubTypeReturnRewrite.java @@ -1,10 +1,14 @@ package io.papermc.asm.rules.method.returns; import io.papermc.asm.ClassProcessingContext; +import io.papermc.asm.rules.RewriteRule; import io.papermc.asm.rules.builder.matcher.method.MethodMatcher; import io.papermc.asm.rules.method.OwnableMethodRewriteRule; import io.papermc.asm.rules.method.rewrite.MethodRewrite; import io.papermc.asm.rules.method.rewrite.SimpleRewrite; +import io.papermc.asm.versioned.ApiVersion; +import io.papermc.asm.versioned.VersionedRuleFactory; +import io.papermc.asm.versioned.matcher.VersionedMethodMatcher; import java.lang.constant.ClassDesc; import java.lang.constant.MethodTypeDesc; import java.util.Set; @@ -32,4 +36,12 @@ public record SubTypeReturnRewrite(Set owners, MethodMatcher methodMa private MethodTypeDesc modifyMethodDescriptor(final MethodTypeDesc methodDescriptor) { return methodDescriptor.changeReturnType(this.newReturnType()); } + + public record Versioned(Set owners, ClassDesc newReturnType, VersionedMethodMatcher versions) implements VersionedRuleFactory { + + @Override + public RewriteRule createRule(final ApiVersion apiVersion) { + return this.versions.ruleForVersion(apiVersion, pair -> new SubTypeReturnRewrite(this.owners(), pair.matcher(), pair.legacyType(), this.newReturnType())); + } + } } diff --git a/src/main/java/io/papermc/asm/rules/rename/EnumRenameBuilder.java b/src/main/java/io/papermc/asm/rules/rename/EnumRenameBuilder.java new file mode 100644 index 0000000..625f37a --- /dev/null +++ b/src/main/java/io/papermc/asm/rules/rename/EnumRenameBuilder.java @@ -0,0 +1,44 @@ +package io.papermc.asm.rules.rename; + +import java.lang.constant.ClassDesc; +import java.util.HashMap; +import java.util.Map; +import org.checkerframework.checker.nullness.qual.Nullable; + +import static io.papermc.asm.util.DescriptorUtils.desc; + +public final class EnumRenameBuilder { + + private final ClassDesc enumTypeDesc; + private @Nullable ClassDesc optionalEnumReplacementImpl; + private final Map enumFieldRenames = new HashMap<>(); + + EnumRenameBuilder(final ClassDesc enumTypeDesc) { + this.enumTypeDesc = enumTypeDesc; + } + + public EnumRenameBuilder enumReplacementImpl(final Class type) { + return this.enumReplacementImpl(desc(type)); + } + + public EnumRenameBuilder enumReplacementImpl(final ClassDesc type) { + if (this.enumTypeDesc.equals(type)) { + throw new IllegalArgumentException("Cannot replace an enum with itself"); + } + this.optionalEnumReplacementImpl = type; + return this; + } + + public EnumRenameBuilder rename(final String legacyName, final String newName) { + this.enumFieldRenames.put(legacyName, newName); + return this; + } + + void apply(final RenameRuleBuilder renameRuleBuilder) { + this.enumFieldRenames.forEach((legacyName, newName) -> { + renameRuleBuilder.fieldByDesc(this.enumTypeDesc, legacyName, newName); + }); + final Map copy = Map.copyOf(this.enumFieldRenames); + renameRuleBuilder.enumValueOfFieldRenames.put(this.enumTypeDesc, new EnumRenamer(this.enumTypeDesc, this.optionalEnumReplacementImpl, copy)); + } +} diff --git a/src/main/java/io/papermc/asm/rules/rename/EnumRenamer.java b/src/main/java/io/papermc/asm/rules/rename/EnumRenamer.java new file mode 100644 index 0000000..d744344 --- /dev/null +++ b/src/main/java/io/papermc/asm/rules/rename/EnumRenamer.java @@ -0,0 +1,8 @@ +package io.papermc.asm.rules.rename; + +import java.lang.constant.ClassDesc; +import java.util.Map; +import org.checkerframework.checker.nullness.qual.Nullable; + +public record EnumRenamer(ClassDesc typeDesc, @Nullable ClassDesc optionalReplacementImpl, Map fieldRenames) { +} diff --git a/src/main/java/io/papermc/asm/rules/rename/EnumValueOfRewriteRule.java b/src/main/java/io/papermc/asm/rules/rename/EnumValueOfRewriteRule.java new file mode 100644 index 0000000..c47da46 --- /dev/null +++ b/src/main/java/io/papermc/asm/rules/rename/EnumValueOfRewriteRule.java @@ -0,0 +1,136 @@ +package io.papermc.asm.rules.rename; + +import io.papermc.asm.rules.builder.matcher.method.MethodMatcher; +import io.papermc.asm.rules.generate.GeneratedMethodHolder; +import io.papermc.asm.rules.method.generated.GeneratedStaticRewrite; +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDescs; +import java.lang.constant.MethodTypeDesc; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import org.objectweb.asm.Label; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.GeneratorAdapter; +import org.objectweb.asm.commons.Method; + +final class EnumValueOfRewriteRule implements GeneratedStaticRewrite { + + private final Set owners = new HashSet<>(); + private final MethodMatcher matcher; + private final Map fieldRenames; + + EnumValueOfRewriteRule(final EnumRenamer renamer) { + this.owners.add(renamer.typeDesc()); + if (renamer.optionalReplacementImpl() != null) { + this.owners.add(renamer.optionalReplacementImpl()); + } + this.matcher = MethodMatcher.builder() + .match("valueOf", b -> b.statik().desc(MethodTypeDesc.of(renamer.typeDesc(), ConstantDescs.CD_String))) + .build(); + this.fieldRenames = new TreeMap<>(renamer.fieldRenames()); + } + + @Override + public void generateMethod(final GeneratorAdapterFactory factory, final MethodCallData modified, final MethodCallData original) { + final GeneratorAdapter methodGenerator = this.createAdapter(factory, modified); + GeneratedMethodHolder.loadParameters(methodGenerator, modified.descriptor()); + final int tableSwitchIndexLocal = methodGenerator.newLocal(Type.INT_TYPE); + methodGenerator.push(-1); + methodGenerator.storeLocal(tableSwitchIndexLocal); + methodGenerator.loadArg(0); + methodGenerator.invokeVirtual(Type.getType(String.class), new Method("hashCode", "()I")); + final Map tableSwitchIndexMap = new LinkedHashMap<>(); + final String[] tableSwitchIndexToRenamedField = new String[this.fieldRenames.size()]; + final Map> hashToField = new LinkedHashMap<>(); + int curIdx = 0; + for (final Map.Entry entry : this.fieldRenames.entrySet()) { + tableSwitchIndexMap.put(entry.getKey(), curIdx); + tableSwitchIndexToRenamedField[curIdx] = entry.getValue(); + curIdx++; + hashToField.computeIfAbsent(entry.getKey().hashCode(), k -> new ArrayList<>()).add(entry.getKey()); + } + final int[] lookupSwitchKeys = hashToField.keySet().stream().mapToInt(Integer::intValue).toArray(); + Arrays.sort(lookupSwitchKeys); + final Label lookupSwitchEndLabel = methodGenerator.newLabel(); // is also default label in this case + final Label[] labels = new Label[lookupSwitchKeys.length]; + for (int i = 0; i < labels.length; i++) { + labels[i] = methodGenerator.newLabel(); + } + methodGenerator.visitLookupSwitchInsn(lookupSwitchEndLabel, lookupSwitchKeys, labels); + for (int i = 0; i < labels.length; i++) { + methodGenerator.mark(labels[i]); + // generate case + final List matchingStrings = hashToField.get(lookupSwitchKeys[i]); + if (matchingStrings.size() == 1) { + methodGenerator.loadArg(0); // load pass string arg + methodGenerator.push(matchingStrings.get(0)); // load maybe matching string + methodGenerator.invokeVirtual(Type.getType(String.class), new Method("equals", "(Ljava/lang/Object;)Z")); + methodGenerator.visitJumpInsn(Opcodes.IFEQ, lookupSwitchEndLabel); + methodGenerator.push(tableSwitchIndexMap.get(matchingStrings.get(0))); + methodGenerator.storeLocal(tableSwitchIndexLocal); + methodGenerator.goTo(lookupSwitchEndLabel); + } else { + final Label[] nestedLabels = new Label[matchingStrings.size()]; + for (int j = 0; j < matchingStrings.size() - 1; j++) { + nestedLabels[j] = methodGenerator.newLabel(); + } + nestedLabels[matchingStrings.size() - 1] = lookupSwitchEndLabel; + for (int j = 0; j < matchingStrings.size(); j++) { + final String maybeMatchingString = matchingStrings.get(j); + methodGenerator.loadArg(0); // load pass string arg + methodGenerator.push(maybeMatchingString); + methodGenerator.invokeVirtual(Type.getType(String.class), new Method("equals", "(Ljava/lang/Object;)Z")); + methodGenerator.visitJumpInsn(Opcodes.IFEQ, nestedLabels[j]); + methodGenerator.push(tableSwitchIndexMap.get(maybeMatchingString)); + methodGenerator.storeLocal(tableSwitchIndexLocal); + methodGenerator.goTo(lookupSwitchEndLabel); + if (nestedLabels[j] != lookupSwitchEndLabel) { + methodGenerator.mark(nestedLabels[j]); // mark start of next label (except last one) + } + } + } + } + methodGenerator.mark(lookupSwitchEndLabel); + methodGenerator.loadLocal(tableSwitchIndexLocal); + final Label[] tableSwitchLabels = new Label[tableSwitchIndexToRenamedField.length]; + for (int i = 0; i < tableSwitchLabels.length; i++) { + tableSwitchLabels[i] = methodGenerator.newLabel(); + } + final Label tableSwitchDefaultLabel = methodGenerator.newLabel(); + final Label tableSwitchEndLabel = methodGenerator.newLabel(); + methodGenerator.visitTableSwitchInsn(0, tableSwitchIndexToRenamedField.length - 1, tableSwitchDefaultLabel, tableSwitchLabels); + for (int i = 0; i < tableSwitchIndexToRenamedField.length; i++) { + methodGenerator.mark(tableSwitchLabels[i]); + methodGenerator.push(tableSwitchIndexToRenamedField[i]); + methodGenerator.goTo(tableSwitchEndLabel); + } + methodGenerator.mark(tableSwitchDefaultLabel); + methodGenerator.loadArg(0); // default to the passed in value + methodGenerator.mark(tableSwitchEndLabel); + methodGenerator.invokeStatic(Type.getType(original.owner().descriptorString()), new Method(original.name(), original.descriptor().descriptorString())); + methodGenerator.returnValue(); + methodGenerator.endMethod(); + } + + @Override + public void generateConstructor(final GeneratorAdapterFactory factory, final MethodCallData modified, final ConstructorCallData original) { + throw new UnsupportedOperationException("EnumValueOfRewriteRule does not support constructor generation"); + } + + @Override + public MethodMatcher methodMatcher() { + return this.matcher; + } + + @Override + public Set owners() { + return this.owners; + } +} diff --git a/src/main/java/io/papermc/asm/rules/rename/RenameRule.java b/src/main/java/io/papermc/asm/rules/rename/RenameRule.java index 1f5f89c..68b80ef 100644 --- a/src/main/java/io/papermc/asm/rules/rename/RenameRule.java +++ b/src/main/java/io/papermc/asm/rules/rename/RenameRule.java @@ -1,126 +1,36 @@ package io.papermc.asm.rules.rename; -import io.papermc.asm.ClassProcessingContext; import io.papermc.asm.rules.RewriteRule; +import io.papermc.asm.rules.rename.asm.FixedClassRemapper; import java.lang.constant.ClassDesc; -import java.lang.constant.MethodTypeDesc; -import java.util.HashMap; +import java.util.ArrayList; +import java.util.List; import java.util.Map; -import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.commons.Remapper; import org.objectweb.asm.commons.SimpleRemapper; -import static io.papermc.asm.util.DescriptorUtils.toOwner; +public final class RenameRule implements RewriteRule.Delegate { -/** - * Applies the provided {@link Remapper} to the bytecode. - * This is better than {@code asm-commons}'s {@link org.objectweb.asm.commons.ClassRemapper} because this - * remaps enum names in annotations. - * - * @param remapper the remapper to apply - */ -public record RenameRule(Remapper remapper) implements RewriteRule { + private final RewriteRule rule; - public static Builder builder() { - return new Builder(); - } - - @Override - public ClassVisitor createVisitor(final int api, final ClassVisitor parent, final ClassProcessingContext context) { - return new FixedClassRemapper(api, parent, this.remapper); - } - - public static final class Builder implements io.papermc.asm.util.Builder { - - private Builder() { - } - - private final Map mappings = new HashMap<>(); - - public Builder methodByDesc(final Iterable owners, final String legacyMethodName, final MethodTypeDesc desc, final String newMethodName) { - for (final ClassDesc owner : owners) { - this.methodByDesc(owner, legacyMethodName, desc, newMethodName); - } - return this; - } - - public Builder methodByDesc(final ClassDesc owner, final String legacyMethodName, final MethodTypeDesc desc, final String newMethodName) { - return this.methodByInternal(toOwner(owner), legacyMethodName, desc.descriptorString(), newMethodName); - } - - public Builder methodByInternal(final Iterable owners, final String legacyMethodName, final MethodTypeDesc desc, final String newMethodName) { - for (final String owner : owners) { - this.methodByInternal(owner, legacyMethodName, desc.descriptorString(), newMethodName); - } - return this; - } + public RenameRule(final Map renames, final Map enumValueOfFieldRenames) { + final Remapper remapper = new SimpleRemapper(Map.copyOf(renames)); - public Builder methodByInternal(final String owner, final String legacyMethodName, final String desc, final String newMethodName) { - this.mappings.put("%s.%s%s".formatted(owner, legacyMethodName, desc), newMethodName); - return this; - } + final List rules = new ArrayList<>(enumValueOfFieldRenames.size() + 1); + enumValueOfFieldRenames.forEach((classDesc, enumRenamer) -> { + rules.add(new EnumValueOfRewriteRule(enumRenamer)); + }); + rules.add((api, parent, context) -> new FixedClassRemapper(api, parent, remapper)); - public Builder fieldsByDesc(final Iterable owners, final String legacyFieldName, final String newFieldName) { - for (final ClassDesc owner : owners) { - this.fieldByDesc(owner, legacyFieldName, newFieldName); - } - return this; - } - - public Builder fieldByDesc(final ClassDesc owner, final String legacyFieldName, final String newFieldName) { - return this.fieldByInternal(toOwner(owner), legacyFieldName, newFieldName); - } - - public Builder fieldByInternal(final Iterable owners, final String legacyFieldName, final String newFieldName) { - for (final String owner : owners) { - this.fieldByInternal(owner, legacyFieldName, newFieldName); - } - return this; - } - - public Builder fieldByInternal(final String owner, final String legacyFieldName, final String newFieldName) { - this.mappings.put("%s.%s".formatted(owner, legacyFieldName), newFieldName); - return this; - } - - /** - * Note that you also have to remap the method for the annotation attribute. - */ - public Builder annotationAttribute(final ClassDesc owner, final String legacyName, final String newName) { - return this.annotationAttribute(owner.descriptorString(), legacyName, newName); - } - - /** - * Note that you also have to remap the method for the annotation attribute. - */ - public Builder annotationAttribute(final String ownerDescriptor, final String legacyName, final String newName) { - if (!ownerDescriptor.startsWith("L") || !ownerDescriptor.endsWith(";")) { - throw new IllegalArgumentException("Invalid owner descriptor: %s".formatted(ownerDescriptor)); - } - // for some reason the remapper wants the Lpkg/name; format, but just for annotation attributes - this.mappings.put("%s.%s".formatted(ownerDescriptor, legacyName), newName); - return this; - } - - /** - * Use {@code /} instead of {@code .}. - */ - public Builder type(final String legacyType, final ClassDesc newType) { - this.mappings.put(legacyType, toOwner(newType)); - return this; - } + this.rule = RewriteRule.chain(rules); + } - /** - * Use {@code /} instead of {@code .}. - */ - public Builder type(final String legacyType, final String newType) { - this.mappings.put(legacyType, newType); - return this; - } + public static RenameRuleBuilder builder() { + return new RenameRuleBuilder(); + } - @Override - public RenameRule build() { - return new RenameRule(new SimpleRemapper(Map.copyOf(this.mappings))); - } + @Override + public RewriteRule delegate() { + return this.rule; } } diff --git a/src/main/java/io/papermc/asm/rules/rename/RenameRuleBuilder.java b/src/main/java/io/papermc/asm/rules/rename/RenameRuleBuilder.java new file mode 100644 index 0000000..c0c9d65 --- /dev/null +++ b/src/main/java/io/papermc/asm/rules/rename/RenameRuleBuilder.java @@ -0,0 +1,116 @@ +package io.papermc.asm.rules.rename; + +import java.lang.constant.ClassDesc; +import java.lang.constant.MethodTypeDesc; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; + +import static io.papermc.asm.util.DescriptorUtils.desc; +import static io.papermc.asm.util.DescriptorUtils.toOwner; + +public final class RenameRuleBuilder implements io.papermc.asm.util.Builder { + + RenameRuleBuilder() { + } + + final Map mappings = new HashMap<>(); + final Map enumValueOfFieldRenames = new HashMap<>(); + + public RenameRuleBuilder methodByDesc(final Iterable owners, final String legacyMethodName, final MethodTypeDesc desc, final String newMethodName) { + for (final ClassDesc owner : owners) { + this.methodByDesc(owner, legacyMethodName, desc, newMethodName); + } + return this; + } + + public RenameRuleBuilder methodByDesc(final ClassDesc owner, final String legacyMethodName, final MethodTypeDesc desc, final String newMethodName) { + return this.methodByInternal(toOwner(owner), legacyMethodName, desc.descriptorString(), newMethodName); + } + + public RenameRuleBuilder methodByInternal(final Iterable owners, final String legacyMethodName, final MethodTypeDesc desc, final String newMethodName) { + for (final String owner : owners) { + this.methodByInternal(owner, legacyMethodName, desc.descriptorString(), newMethodName); + } + return this; + } + + public RenameRuleBuilder methodByInternal(final String owner, final String legacyMethodName, final String desc, final String newMethodName) { + this.mappings.put("%s.%s%s".formatted(owner, legacyMethodName, desc), newMethodName); + return this; + } + + public RenameRuleBuilder fieldByDesc(final Iterable owners, final String legacyFieldName, final String newFieldName) { + for (final ClassDesc owner : owners) { + this.fieldByDesc(owner, legacyFieldName, newFieldName); + } + return this; + } + + public RenameRuleBuilder fieldByDesc(final ClassDesc owner, final String legacyFieldName, final String newFieldName) { + return this.fieldByInternal(toOwner(owner), legacyFieldName, newFieldName); + } + + public RenameRuleBuilder fieldByInternal(final Iterable owners, final String legacyFieldName, final String newFieldName) { + for (final String owner : owners) { + this.fieldByInternal(owner, legacyFieldName, newFieldName); + } + return this; + } + + public RenameRuleBuilder fieldByInternal(final String owner, final String legacyFieldName, final String newFieldName) { + this.mappings.put("%s.%s".formatted(owner, legacyFieldName), newFieldName); + return this; + } + + /** + * Note that you also have to remap the method for the annotation attribute. + */ + public RenameRuleBuilder annotationAttribute(final ClassDesc owner, final String legacyName, final String newName) { + return this.annotationAttribute(owner.descriptorString(), legacyName, newName); + } + + /** + * Note that you also have to remap the method for the annotation attribute. + */ + public RenameRuleBuilder annotationAttribute(final String ownerDescriptor, final String legacyName, final String newName) { + if (!ownerDescriptor.startsWith("L") || !ownerDescriptor.endsWith(";")) { + throw new IllegalArgumentException("Invalid owner descriptor: %s".formatted(ownerDescriptor)); + } + // for some reason the remapper wants the Lpkg/name; format, but just for annotation attributes + this.mappings.put("%s.%s".formatted(ownerDescriptor, legacyName), newName); + return this; + } + + /** + * Use {@code /} instead of {@code .}. + */ + public RenameRuleBuilder type(final String legacyType, final ClassDesc newType) { + this.mappings.put(legacyType, toOwner(newType)); + return this; + } + + /** + * Use {@code /} instead of {@code .}. + */ + public RenameRuleBuilder type(final String legacyType, final String newType) { + this.mappings.put(legacyType, newType); + return this; + } + + public RenameRuleBuilder editEnum(final Class enumTypeDesc, final Consumer renamer) { + return this.editEnum(desc(enumTypeDesc), renamer); + } + + public RenameRuleBuilder editEnum(final ClassDesc enumTypeDesc, final Consumer renamer) { + final EnumRenameBuilder enumRenamer = new EnumRenameBuilder(enumTypeDesc); + renamer.accept(enumRenamer); + enumRenamer.apply(this); + return this; + } + + @Override + public RenameRule build() { + return new RenameRule(this.mappings, this.enumValueOfFieldRenames); + } +} diff --git a/src/main/java/io/papermc/asm/rules/rename/FixedAnnotationRemapper.java b/src/main/java/io/papermc/asm/rules/rename/asm/FixedAnnotationRemapper.java similarity index 96% rename from src/main/java/io/papermc/asm/rules/rename/FixedAnnotationRemapper.java rename to src/main/java/io/papermc/asm/rules/rename/asm/FixedAnnotationRemapper.java index 21884c4..927baba 100644 --- a/src/main/java/io/papermc/asm/rules/rename/FixedAnnotationRemapper.java +++ b/src/main/java/io/papermc/asm/rules/rename/asm/FixedAnnotationRemapper.java @@ -1,4 +1,4 @@ -package io.papermc.asm.rules.rename; +package io.papermc.asm.rules.rename.asm; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.Type; diff --git a/src/main/java/io/papermc/asm/rules/rename/FixedClassRemapper.java b/src/main/java/io/papermc/asm/rules/rename/asm/FixedClassRemapper.java similarity index 97% rename from src/main/java/io/papermc/asm/rules/rename/FixedClassRemapper.java rename to src/main/java/io/papermc/asm/rules/rename/asm/FixedClassRemapper.java index 3522773..a5c5484 100644 --- a/src/main/java/io/papermc/asm/rules/rename/FixedClassRemapper.java +++ b/src/main/java/io/papermc/asm/rules/rename/asm/FixedClassRemapper.java @@ -1,4 +1,4 @@ -package io.papermc.asm.rules.rename; +package io.papermc.asm.rules.rename.asm; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.ClassVisitor; diff --git a/src/main/java/io/papermc/asm/rules/rename/FixedFieldRemapper.java b/src/main/java/io/papermc/asm/rules/rename/asm/FixedFieldRemapper.java similarity index 93% rename from src/main/java/io/papermc/asm/rules/rename/FixedFieldRemapper.java rename to src/main/java/io/papermc/asm/rules/rename/asm/FixedFieldRemapper.java index fe59165..1dcc298 100644 --- a/src/main/java/io/papermc/asm/rules/rename/FixedFieldRemapper.java +++ b/src/main/java/io/papermc/asm/rules/rename/asm/FixedFieldRemapper.java @@ -1,4 +1,4 @@ -package io.papermc.asm.rules.rename; +package io.papermc.asm.rules.rename.asm; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.FieldVisitor; diff --git a/src/main/java/io/papermc/asm/rules/rename/FixedMethodRemapper.java b/src/main/java/io/papermc/asm/rules/rename/asm/FixedMethodRemapper.java similarity index 94% rename from src/main/java/io/papermc/asm/rules/rename/FixedMethodRemapper.java rename to src/main/java/io/papermc/asm/rules/rename/asm/FixedMethodRemapper.java index 2089e50..fa094b5 100644 --- a/src/main/java/io/papermc/asm/rules/rename/FixedMethodRemapper.java +++ b/src/main/java/io/papermc/asm/rules/rename/asm/FixedMethodRemapper.java @@ -1,4 +1,4 @@ -package io.papermc.asm.rules.rename; +package io.papermc.asm.rules.rename.asm; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.MethodVisitor; diff --git a/src/main/java/io/papermc/asm/rules/rename/FixedRecordComponentRemapper.java b/src/main/java/io/papermc/asm/rules/rename/asm/FixedRecordComponentRemapper.java similarity index 94% rename from src/main/java/io/papermc/asm/rules/rename/FixedRecordComponentRemapper.java rename to src/main/java/io/papermc/asm/rules/rename/asm/FixedRecordComponentRemapper.java index e18851c..1b065aa 100644 --- a/src/main/java/io/papermc/asm/rules/rename/FixedRecordComponentRemapper.java +++ b/src/main/java/io/papermc/asm/rules/rename/asm/FixedRecordComponentRemapper.java @@ -1,4 +1,4 @@ -package io.papermc.asm.rules.rename; +package io.papermc.asm.rules.rename.asm; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.RecordComponentVisitor; diff --git a/src/main/java/io/papermc/asm/versioned/CachingVersionedRuleFactory.java b/src/main/java/io/papermc/asm/versioned/CachingVersionedRuleFactory.java new file mode 100644 index 0000000..ed9bc09 --- /dev/null +++ b/src/main/java/io/papermc/asm/versioned/CachingVersionedRuleFactory.java @@ -0,0 +1,28 @@ +package io.papermc.asm.versioned; + +import io.papermc.asm.rules.RewriteRule; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.jetbrains.annotations.ApiStatus; + +public abstract class CachingVersionedRuleFactory implements VersionedRuleFactory { + + private final Map cache = new ConcurrentHashMap<>(); + private @MonotonicNonNull VersionedRuleFactory rootFactory; + + @ApiStatus.OverrideOnly + public abstract VersionedRuleFactory createRootFactory(); + + protected final VersionedRuleFactory rootFactory() { + if (this.rootFactory == null) { + this.rootFactory = this.createRootFactory(); + } + return this.rootFactory; + } + + @Override + public final RewriteRule createRule(final ApiVersion apiVersion) { + return this.cache.computeIfAbsent(apiVersion, this.rootFactory()::createRule); + } +} diff --git a/src/main/java/io/papermc/asm/versioned/VersionedRuleFactory.java b/src/main/java/io/papermc/asm/versioned/VersionedRuleFactory.java index bfcc524..9940868 100644 --- a/src/main/java/io/papermc/asm/versioned/VersionedRuleFactory.java +++ b/src/main/java/io/papermc/asm/versioned/VersionedRuleFactory.java @@ -1,28 +1,63 @@ package io.papermc.asm.versioned; import io.papermc.asm.rules.RewriteRule; +import io.papermc.asm.util.DescriptorUtils; +import java.lang.constant.ClassDesc; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.List; -import org.checkerframework.checker.nullness.qual.NonNull; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Collectors; import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.framework.qual.DefaultQualifier; -@DefaultQualifier(NonNull.class) public interface VersionedRuleFactory { + VersionedRuleFactory EMPTY = apiVersion -> RewriteRule.EMPTY; + + @SafeVarargs + static VersionedRuleFactory forOwnerClass(final Class owner, final Consumer firstFactoryConsumer, final Consumer... factoryConsumers) { + return forOwnerClasses(Collections.singleton(owner), firstFactoryConsumer, factoryConsumers); + } + + @SafeVarargs + static VersionedRuleFactory forOwnerClasses(final Set> owners, final Consumer firstFactoryConsumer, final Consumer... factoryConsumers) { + return forOwners(owners.stream().map(DescriptorUtils::desc).collect(Collectors.toUnmodifiableSet()), firstFactoryConsumer, factoryConsumers); + } + + @SafeVarargs + static VersionedRuleFactory forOwner(final ClassDesc owner, final Consumer firstFactoryConsumer, final Consumer... factoryConsumers) { + return forOwners(Collections.singleton(owner), firstFactoryConsumer, factoryConsumers); + } + + @SafeVarargs + static VersionedRuleFactory forOwners(final Set owners, final Consumer firstFactoryConsumer, final Consumer... factoryConsumers) { + final VersionedRuleFactoryBuilder factory = VersionedRuleFactoryBuilder.create(owners); + firstFactoryConsumer.accept(factory); + for (final Consumer factoryConsumer : factoryConsumers) { + factoryConsumer.accept(factory); + } + return factory.build(); + } + static VersionedRuleFactory chain(final VersionedRuleFactory... factories) { return chain(Arrays.asList(factories)); } static VersionedRuleFactory chain(final Collection factories) { + if (factories.isEmpty()) { + return EMPTY; + } else if (factories.size() == 1) { + return factories.iterator().next(); + } return new Chain(List.copyOf(factories)); } - @Nullable RewriteRule createRule(ApiVersion apiVersion); + RewriteRule createRule(ApiVersion apiVersion); - record Chain(List factories) implements VersionedRuleFactory{ + record Chain(List factories) implements VersionedRuleFactory { public Chain { factories = List.copyOf(factories); @@ -33,7 +68,7 @@ public RewriteRule createRule(final ApiVersion apiVersion) { final List rules = new ArrayList<>(); for (final VersionedRuleFactory factory : this.factories) { final @Nullable RewriteRule rule = factory.createRule(apiVersion); - if (rule != null) { + if (rule != RewriteRule.EMPTY) { rules.add(rule); } } diff --git a/src/main/java/io/papermc/asm/versioned/VersionedRuleFactoryBuilder.java b/src/main/java/io/papermc/asm/versioned/VersionedRuleFactoryBuilder.java index d664cfb..d0c85ca 100644 --- a/src/main/java/io/papermc/asm/versioned/VersionedRuleFactoryBuilder.java +++ b/src/main/java/io/papermc/asm/versioned/VersionedRuleFactoryBuilder.java @@ -1,12 +1,12 @@ package io.papermc.asm.versioned; +import io.papermc.asm.rules.builder.matcher.method.MethodMatcher; import io.papermc.asm.rules.builder.matcher.method.targeted.TargetedMethodMatcher; +import io.papermc.asm.versioned.matcher.VersionedMethodMatcher; +import io.papermc.asm.versioned.matcher.targeted.VersionedTargetedMethodMatcher; import java.lang.constant.ClassDesc; import java.lang.reflect.Method; -import java.util.Map; -import java.util.NavigableMap; import java.util.Set; -import java.util.TreeMap; import static io.papermc.asm.util.DescriptorUtils.desc; @@ -16,19 +16,71 @@ static VersionedRuleFactoryBuilder create(final Set owners) { return new VersionedRuleFactoryBuilderImpl(owners); } - default void changeParamDirect(final Class newParamType, final ApiVersion apiVersion, final TargetedMethodMatcher methodMatcher, final Method staticHandler) { - this.changeParamDirect(desc(newParamType), apiVersion, methodMatcher, staticHandler); + // plain static rewrite + + // + default void changeParamToSuper(final Class newParamType, final ApiVersion apiVersion, final Class legacyParamType, final MethodMatcher methodMatcher) { + this.changeParamToSuper(desc(newParamType), apiVersion, desc(legacyParamType), methodMatcher); + } + + default void changeParamToSuper(final ClassDesc newParamType, final ApiVersion apiVersion, final ClassDesc legacyParamType, final MethodMatcher methodMatcher) { + this.changeParamToSuper(newParamType, VersionedMethodMatcher.single(apiVersion, methodMatcher, legacyParamType)); + } + + default void changeParamToSuper(final Class newParamType, final VersionedMethodMatcher versions) { + this.changeParamToSuper(desc(newParamType), versions); + } + + void changeParamToSuper(ClassDesc newParamType, VersionedMethodMatcher versions); + // + + // + default void changeParamFuzzy(final Class newParamType, final ApiVersion apiVersion, final Method staticHandler, final TargetedMethodMatcher targetedMethodMatcher) { + this.changeParamFuzzy(desc(newParamType), apiVersion, staticHandler, targetedMethodMatcher); + } + + default void changeParamFuzzy(final ClassDesc newParamType, final ApiVersion apiVersion, final Method staticHandler, final TargetedMethodMatcher targetedMethodMatcher) { + this.changeParamFuzzy(newParamType, VersionedTargetedMethodMatcher.single(apiVersion, targetedMethodMatcher, staticHandler)); + } + + default void changeParamFuzzy(final Class newParamType, final VersionedTargetedMethodMatcher versions) { + this.changeParamFuzzy(desc(newParamType), versions); + } + + void changeParamFuzzy(ClassDesc newParamType, VersionedTargetedMethodMatcher versions); + // + + // + default void changeParamDirect(final Class newParamType, final ApiVersion apiVersion, final Method staticHandler, final TargetedMethodMatcher methodMatcher) { + this.changeParamDirect(desc(newParamType), apiVersion, staticHandler, methodMatcher); } - default void changeParamDirect(final ClassDesc newParamType, final ApiVersion apiVersion, final TargetedMethodMatcher methodMatcher, final Method staticHandler) { - this.changeParamDirect(newParamType, new TreeMap<>(Map.of(apiVersion, Map.entry(methodMatcher, staticHandler)))); + default void changeParamDirect(final ClassDesc newParamType, final ApiVersion apiVersion, final Method staticHandler, final TargetedMethodMatcher methodMatcher) { + this.changeParamDirect(newParamType, VersionedTargetedMethodMatcher.single(apiVersion, methodMatcher, staticHandler)); } - default void changeParamDirect(final Class newParamType, final NavigableMap> versions) { + default void changeParamDirect(final Class newParamType, final VersionedTargetedMethodMatcher versions) { this.changeParamDirect(desc(newParamType), versions); } - void changeParamDirect(ClassDesc newParamType, NavigableMap> versions); + void changeParamDirect(ClassDesc newParamType, VersionedTargetedMethodMatcher versions); + // + + // + default void changeReturnTypeToSub(final Class newReturnType, final ApiVersion apiVersion, final Class legacyReturnType, final MethodMatcher methodMatcher) { + this.changeReturnTypeToSub(desc(newReturnType), apiVersion, desc(legacyReturnType), methodMatcher); + } + + default void changeReturnTypeToSub(final ClassDesc newReturnType, final ApiVersion apiVersion, final ClassDesc legacyReturnType, final MethodMatcher methodMatcher) { + this.changeReturnTypeToSub(newReturnType, VersionedMethodMatcher.single(apiVersion, methodMatcher, legacyReturnType)); + } + + default void changeReturnTypeToSub(final Class newReturnType, final VersionedMethodMatcher versions) { + this.changeReturnTypeToSub(desc(newReturnType), versions); + } + + void changeReturnTypeToSub(ClassDesc newReturnType, VersionedMethodMatcher versions); + // VersionedRuleFactory build(); } diff --git a/src/main/java/io/papermc/asm/versioned/VersionedRuleFactoryBuilderImpl.java b/src/main/java/io/papermc/asm/versioned/VersionedRuleFactoryBuilderImpl.java index b7b7455..0e1460d 100644 --- a/src/main/java/io/papermc/asm/versioned/VersionedRuleFactoryBuilderImpl.java +++ b/src/main/java/io/papermc/asm/versioned/VersionedRuleFactoryBuilderImpl.java @@ -1,13 +1,14 @@ package io.papermc.asm.versioned; -import io.papermc.asm.rules.builder.matcher.method.targeted.TargetedMethodMatcher; import io.papermc.asm.rules.method.params.DirectParameterRewrite; +import io.papermc.asm.rules.method.params.FuzzyParameterRewrite; +import io.papermc.asm.rules.method.params.SuperTypeParamRewrite; +import io.papermc.asm.rules.method.returns.SubTypeReturnRewrite; +import io.papermc.asm.versioned.matcher.VersionedMethodMatcher; +import io.papermc.asm.versioned.matcher.targeted.VersionedTargetedMethodMatcher; import java.lang.constant.ClassDesc; -import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; -import java.util.Map; -import java.util.NavigableMap; import java.util.Set; public class VersionedRuleFactoryBuilderImpl implements VersionedRuleFactoryBuilder { @@ -20,15 +21,27 @@ public VersionedRuleFactoryBuilderImpl(final Set owners) { } @Override - public void changeParamDirect(final ClassDesc newParamType, final NavigableMap> versions) { + public void changeParamToSuper(final ClassDesc newParamType, final VersionedMethodMatcher versions) { + this.factories.add(new SuperTypeParamRewrite.Versioned(this.owners, newParamType, versions)); + } + + @Override + public void changeParamFuzzy(final ClassDesc newParamType, final VersionedTargetedMethodMatcher versions) { + this.factories.add(new FuzzyParameterRewrite.Versioned(this.owners, newParamType, versions)); + } + + @Override + public void changeParamDirect(final ClassDesc newParamType, final VersionedTargetedMethodMatcher versions) { this.factories.add(new DirectParameterRewrite.Versioned(this.owners, newParamType, versions)); } + @Override + public void changeReturnTypeToSub(final ClassDesc newReturnType, final VersionedMethodMatcher versions) { + this.factories.add(new SubTypeReturnRewrite.Versioned(this.owners, newReturnType, versions)); + } + @Override public VersionedRuleFactory build() { - if (this.factories.size() == 1) { - return this.factories.get(0); - } return VersionedRuleFactory.chain(this.factories); } } diff --git a/src/main/java/io/papermc/asm/versioned/builder/VersionedMethodMatcherBuilder.java b/src/main/java/io/papermc/asm/versioned/builder/VersionedMethodMatcherBuilder.java deleted file mode 100644 index 0028a69..0000000 --- a/src/main/java/io/papermc/asm/versioned/builder/VersionedMethodMatcherBuilder.java +++ /dev/null @@ -1,4 +0,0 @@ -package io.papermc.asm.versioned.builder; - -public interface VersionedMethodMatcherBuilder { -} diff --git a/src/main/java/io/papermc/asm/versioned/matcher/VersionedMatcherBase.java b/src/main/java/io/papermc/asm/versioned/matcher/VersionedMatcherBase.java new file mode 100644 index 0000000..f558dec --- /dev/null +++ b/src/main/java/io/papermc/asm/versioned/matcher/VersionedMatcherBase.java @@ -0,0 +1,25 @@ +package io.papermc.asm.versioned.matcher; + +import io.papermc.asm.rules.RewriteRule; +import io.papermc.asm.versioned.ApiVersion; +import java.util.Map; +import java.util.NavigableMap; +import java.util.function.Function; +import org.checkerframework.checker.nullness.qual.Nullable; + +public abstract class VersionedMatcherBase

{ + + private final NavigableMap map; + + protected VersionedMatcherBase(final NavigableMap map) { + this.map = map; + } + + public RewriteRule ruleForVersion(final ApiVersion version, final Function creator) { + final Map.@Nullable Entry entry = this.map.ceilingEntry(version); + if (entry == null) { + return RewriteRule.EMPTY; + } + return creator.apply(entry.getValue()); + } +} diff --git a/src/main/java/io/papermc/asm/versioned/matcher/VersionedMethodMatcher.java b/src/main/java/io/papermc/asm/versioned/matcher/VersionedMethodMatcher.java new file mode 100644 index 0000000..c15b8d4 --- /dev/null +++ b/src/main/java/io/papermc/asm/versioned/matcher/VersionedMethodMatcher.java @@ -0,0 +1,26 @@ +package io.papermc.asm.versioned.matcher; + +import io.papermc.asm.rules.builder.matcher.method.MethodMatcher; +import io.papermc.asm.versioned.ApiVersion; +import java.lang.constant.ClassDesc; +import java.util.Map; +import java.util.NavigableMap; +import java.util.TreeMap; + +public class VersionedMethodMatcher extends VersionedMatcherBase { + + public static VersionedMethodMatcherBuilder builder() { + return new VersionedMethodMatcherBuilderImpl(); + } + + public static VersionedMethodMatcher single(final ApiVersion apiVersion, final MethodMatcher matcher, final ClassDesc legacyType) { + return new VersionedMethodMatcher(new TreeMap<>(Map.of(apiVersion, new Pair(matcher, legacyType)))); + } + + VersionedMethodMatcher(final NavigableMap map) { + super(map); + } + + public record Pair(MethodMatcher matcher, ClassDesc legacyType) { + } +} diff --git a/src/main/java/io/papermc/asm/versioned/matcher/VersionedMethodMatcherBuilder.java b/src/main/java/io/papermc/asm/versioned/matcher/VersionedMethodMatcherBuilder.java new file mode 100644 index 0000000..7cc7552 --- /dev/null +++ b/src/main/java/io/papermc/asm/versioned/matcher/VersionedMethodMatcherBuilder.java @@ -0,0 +1,13 @@ +package io.papermc.asm.versioned.matcher; + +import io.papermc.asm.rules.builder.matcher.method.MethodMatcher; +import io.papermc.asm.util.Builder; +import io.papermc.asm.versioned.ApiVersion; +import java.lang.constant.ClassDesc; +import org.jetbrains.annotations.Contract; + +public interface VersionedMethodMatcherBuilder extends Builder { + + @Contract(value = "_, _, _ -> this", mutates = "this") + VersionedMethodMatcherBuilder with(ApiVersion apiVersion, MethodMatcher matcher, ClassDesc oldType); +} diff --git a/src/main/java/io/papermc/asm/versioned/matcher/VersionedMethodMatcherBuilderImpl.java b/src/main/java/io/papermc/asm/versioned/matcher/VersionedMethodMatcherBuilderImpl.java new file mode 100644 index 0000000..9d3b2e0 --- /dev/null +++ b/src/main/java/io/papermc/asm/versioned/matcher/VersionedMethodMatcherBuilderImpl.java @@ -0,0 +1,26 @@ +package io.papermc.asm.versioned.matcher; + +import io.papermc.asm.rules.builder.matcher.method.MethodMatcher; +import io.papermc.asm.versioned.ApiVersion; +import java.lang.constant.ClassDesc; +import java.util.NavigableMap; +import java.util.TreeMap; + +public class VersionedMethodMatcherBuilderImpl implements VersionedMethodMatcherBuilder { + + private final NavigableMap versions = new TreeMap<>(); + + @Override + public VersionedMethodMatcherBuilder with(final ApiVersion apiVersion, final MethodMatcher matcher, final ClassDesc legacyType) { + if (this.versions.containsKey(apiVersion)) { + throw new IllegalArgumentException("Duplicate version: " + apiVersion); + } + this.versions.put(apiVersion, new VersionedMethodMatcher.Pair(matcher, legacyType)); + return this; + } + + @Override + public VersionedMethodMatcher build() { + return new VersionedMethodMatcher(this.versions); + } +} diff --git a/src/main/java/io/papermc/asm/versioned/matcher/targeted/VersionedTargetedMethodMatcher.java b/src/main/java/io/papermc/asm/versioned/matcher/targeted/VersionedTargetedMethodMatcher.java new file mode 100644 index 0000000..d4749e8 --- /dev/null +++ b/src/main/java/io/papermc/asm/versioned/matcher/targeted/VersionedTargetedMethodMatcher.java @@ -0,0 +1,27 @@ +package io.papermc.asm.versioned.matcher.targeted; + +import io.papermc.asm.rules.builder.matcher.method.targeted.TargetedMethodMatcher; +import io.papermc.asm.versioned.ApiVersion; +import io.papermc.asm.versioned.matcher.VersionedMatcherBase; +import java.lang.reflect.Method; +import java.util.Map; +import java.util.NavigableMap; +import java.util.TreeMap; + +public final class VersionedTargetedMethodMatcher extends VersionedMatcherBase { + + public static VersionedTargetedMethodMatcherBuilder builder() { + return new VersionedTargetedMethodMatcherBuilderImpl(); + } + + public static VersionedTargetedMethodMatcher single(final ApiVersion apiVersion, final TargetedMethodMatcher matcher, final Method staticHandler) { + return new VersionedTargetedMethodMatcher(new TreeMap<>(Map.of(apiVersion, new Pair(matcher, staticHandler)))); + } + + VersionedTargetedMethodMatcher(final NavigableMap map) { + super(map); + } + + public record Pair(TargetedMethodMatcher matcher, Method staticHandler) { + } +} diff --git a/src/main/java/io/papermc/asm/versioned/builder/VersionedTargetedMethodMatcherBuilder.java b/src/main/java/io/papermc/asm/versioned/matcher/targeted/VersionedTargetedMethodMatcherBuilder.java similarity index 53% rename from src/main/java/io/papermc/asm/versioned/builder/VersionedTargetedMethodMatcherBuilder.java rename to src/main/java/io/papermc/asm/versioned/matcher/targeted/VersionedTargetedMethodMatcherBuilder.java index 21771d9..2634642 100644 --- a/src/main/java/io/papermc/asm/versioned/builder/VersionedTargetedMethodMatcherBuilder.java +++ b/src/main/java/io/papermc/asm/versioned/matcher/targeted/VersionedTargetedMethodMatcherBuilder.java @@ -1,19 +1,13 @@ -package io.papermc.asm.versioned.builder; +package io.papermc.asm.versioned.matcher.targeted; import io.papermc.asm.rules.builder.matcher.method.targeted.TargetedMethodMatcher; import io.papermc.asm.util.Builder; import io.papermc.asm.versioned.ApiVersion; import java.lang.reflect.Method; -import java.util.Map; -import java.util.NavigableMap; import org.jetbrains.annotations.Contract; -public interface VersionedTargetedMethodMatcherBuilder extends Builder>> { - - static VersionedTargetedMethodMatcherBuilder builder() { - return new VersionedTargetedMethodMatcherBuilderImpl(); - } +public interface VersionedTargetedMethodMatcherBuilder extends Builder { @Contract(value = "_, _, _ -> this", mutates = "this") - VersionedTargetedMethodMatcherBuilder with(ApiVersion apiVersion, TargetedMethodMatcher targetedMethodMatcher, Method method); + VersionedTargetedMethodMatcherBuilder with(ApiVersion apiVersion, TargetedMethodMatcher matcher, Method staticHandler); } diff --git a/src/main/java/io/papermc/asm/versioned/builder/VersionedTargetedMethodMatcherBuilderImpl.java b/src/main/java/io/papermc/asm/versioned/matcher/targeted/VersionedTargetedMethodMatcherBuilderImpl.java similarity index 52% rename from src/main/java/io/papermc/asm/versioned/builder/VersionedTargetedMethodMatcherBuilderImpl.java rename to src/main/java/io/papermc/asm/versioned/matcher/targeted/VersionedTargetedMethodMatcherBuilderImpl.java index 04f39df..2b7b53c 100644 --- a/src/main/java/io/papermc/asm/versioned/builder/VersionedTargetedMethodMatcherBuilderImpl.java +++ b/src/main/java/io/papermc/asm/versioned/matcher/targeted/VersionedTargetedMethodMatcherBuilderImpl.java @@ -1,28 +1,26 @@ -package io.papermc.asm.versioned.builder; +package io.papermc.asm.versioned.matcher.targeted; import io.papermc.asm.rules.builder.matcher.method.targeted.TargetedMethodMatcher; import io.papermc.asm.versioned.ApiVersion; import java.lang.reflect.Method; -import java.util.Collections; -import java.util.Map; import java.util.NavigableMap; import java.util.TreeMap; public class VersionedTargetedMethodMatcherBuilderImpl implements VersionedTargetedMethodMatcherBuilder { - private final NavigableMap> versions = new TreeMap<>(); + private final NavigableMap versions = new TreeMap<>(); @Override - public VersionedTargetedMethodMatcherBuilder with(final ApiVersion apiVersion, final TargetedMethodMatcher targetedMethodMatcher, final Method method) { + public VersionedTargetedMethodMatcherBuilder with(final ApiVersion apiVersion, final TargetedMethodMatcher matcher, final Method staticHandler) { if (this.versions.containsKey(apiVersion)) { throw new IllegalArgumentException("Duplicate version: " + apiVersion); } - this.versions.put(apiVersion, Map.entry(targetedMethodMatcher, method)); + this.versions.put(apiVersion, new VersionedTargetedMethodMatcher.Pair(matcher, staticHandler)); return this; } @Override - public NavigableMap> build() { - return Collections.unmodifiableNavigableMap(this.versions); + public VersionedTargetedMethodMatcher build() { + return new VersionedTargetedMethodMatcher(this.versions); } } diff --git a/src/main/java/io/papermc/asm/versioned/package-info.java b/src/main/java/io/papermc/asm/versioned/package-info.java new file mode 100644 index 0000000..6e8e9a3 --- /dev/null +++ b/src/main/java/io/papermc/asm/versioned/package-info.java @@ -0,0 +1,5 @@ +@DefaultQualifier(NonNull.class) +package io.papermc.asm.versioned; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; diff --git a/src/test/java/io/papermc/asm/ApiVersion.java b/src/test/java/io/papermc/asm/ApiVersion.java new file mode 100644 index 0000000..cb7606f --- /dev/null +++ b/src/test/java/io/papermc/asm/ApiVersion.java @@ -0,0 +1,48 @@ +package io.papermc.asm; + +import java.util.List; +import java.util.Objects; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; + +@DefaultQualifier(NonNull.class) +public class ApiVersion implements io.papermc.asm.versioned.ApiVersion { + + public static final ApiVersion ONE = new ApiVersion(1); + public static final ApiVersion TWO = new ApiVersion(2); + public static final ApiVersion THREE = new ApiVersion(3); + public static final ApiVersion FOUR = new ApiVersion(4); + + public static final List ALL_VERSIONS = List.of(ONE, TWO, THREE, FOUR); + + private final int version; + + public ApiVersion(final int version) { + this.version = version; + } + + @Override + public int compareTo(final io.papermc.asm.versioned.ApiVersion o) { + return Integer.compare(this.version, ((ApiVersion) o).version); + } + + @Override + public String toString() { + return "ApiVersion{" + + "version=" + this.version + + '}'; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || this.getClass() != o.getClass()) return false; + final ApiVersion that = (ApiVersion) o; + return this.version == that.version; + } + + @Override + public int hashCode() { + return Objects.hashCode(this.version); + } +} diff --git a/src/test/java/io/papermc/asm/TestUtil.java b/src/test/java/io/papermc/asm/TestUtil.java index 88fa2c5..5d9a926 100644 --- a/src/test/java/io/papermc/asm/TestUtil.java +++ b/src/test/java/io/papermc/asm/TestUtil.java @@ -54,9 +54,9 @@ public byte[] process(final byte[] bytes) { final ClassReader classReader = new ClassReader(bytes); final ClassWriter classWriter; if (this.copyFromClassReader()) { - classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS); + classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); } else { - classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); + classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); } classReader.accept(this.factory.createVisitor(classWriter), 0); return classWriter.toByteArray(); diff --git a/src/test/java/io/papermc/asm/rules/methods/MethodRewritesTest.java b/src/test/java/io/papermc/asm/rules/methods/MethodRewritesTest.java index 68c7dde..223e0bc 100644 --- a/src/test/java/io/papermc/asm/rules/methods/MethodRewritesTest.java +++ b/src/test/java/io/papermc/asm/rules/methods/MethodRewritesTest.java @@ -3,12 +3,21 @@ import data.methods.Methods; import data.types.hierarchy.Entity; import data.types.hierarchy.Player; +import io.papermc.asm.ApiVersion; import io.papermc.asm.TransformerTest; import io.papermc.asm.checks.TransformerCheck; import io.papermc.asm.rules.RewriteRule; import io.papermc.asm.rules.builder.matcher.method.MethodMatcher; import io.papermc.asm.rules.builder.matcher.method.MethodMatcherBuilder; +import io.papermc.asm.rules.method.params.SuperTypeParamRewrite; +import io.papermc.asm.rules.method.returns.SubTypeReturnRewrite; +import io.papermc.asm.versioned.VersionedRuleFactory; +import io.papermc.asm.versioned.VersionedTester; +import io.papermc.asm.versioned.matcher.VersionedMethodMatcher; import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDescs; +import java.util.Map; +import org.junit.jupiter.api.Test; class MethodRewritesTest { @@ -32,6 +41,26 @@ void testSuperTypeParam(final TransformerCheck check) { check.run(rule); } + @Test + void testVersionedSuperTypeParam() { + final VersionedRuleFactory factory = VersionedRuleFactory.forOwnerClass(String.class, builder -> { + final MethodMatcher method1 = MethodMatcher.builder().match("method1").build(); + builder.changeParamToSuper( + String.class, + VersionedMethodMatcher.builder() + .with(ApiVersion.ONE, method1, ConstantDescs.CD_int) + .with(ApiVersion.THREE, method1, ConstantDescs.CD_long) + .build() + ); + }); + + final VersionedTester tester = new VersionedTester(factory, ApiVersion.ALL_VERSIONS); + tester.test(SuperTypeParamRewrite::oldParamType, Map.of( + ApiVersion.ONE, ConstantDescs.CD_int, + ApiVersion.THREE, ConstantDescs.CD_long + )); + } + @TransformerTest("data.methods.inplace.SubTypeReturnUser") void testSubTypeReturn(final TransformerCheck check) { final RewriteRule rule = RewriteRule.forOwnerClass(Methods.class, builder -> { @@ -47,4 +76,24 @@ void testSubTypeReturn(final TransformerCheck check) { }); check.run(rule); } + + @Test + void testVersionedSubTypeReturn() { + final VersionedRuleFactory factory = VersionedRuleFactory.forOwnerClass(String.class, builder -> { + final MethodMatcher method1 = MethodMatcher.builder().match("method1").build(); + builder.changeReturnTypeToSub( + String.class, + VersionedMethodMatcher.builder() + .with(ApiVersion.ONE, method1, ConstantDescs.CD_int) + .with(ApiVersion.THREE, method1, ConstantDescs.CD_long) + .build() + ); + }); + + final VersionedTester tester = new VersionedTester(factory, ApiVersion.ALL_VERSIONS); + tester.test(SubTypeReturnRewrite::oldReturnType, Map.of( + ApiVersion.ONE, ConstantDescs.CD_int, + ApiVersion.THREE, ConstantDescs.CD_long + )); + } } diff --git a/src/test/java/io/papermc/asm/rules/rename/RenameRuleTest.java b/src/test/java/io/papermc/asm/rules/rename/RenameRuleTest.java index 80ee3b8..fbd4ac7 100644 --- a/src/test/java/io/papermc/asm/rules/rename/RenameRuleTest.java +++ b/src/test/java/io/papermc/asm/rules/rename/RenameRuleTest.java @@ -1,5 +1,6 @@ package io.papermc.asm.rules.rename; +import data.types.rename.TestEnum; import io.papermc.asm.TransformerTest; import io.papermc.asm.checks.TransformerCheck; @@ -9,9 +10,14 @@ class RenameRuleTest { void testAnnotationSpecificRenames(final TransformerCheck check) { final RenameRule rule = RenameRule.builder() .type("data/types/rename/TestEnum", "data/types/rename/RenamedTestEnum") - .fieldByInternal("data/types/rename/TestEnum", "A", "ONE") - .fieldByInternal("data/types/rename/TestEnum", "B", "TWO") - .fieldByInternal("data/types/rename/TestEnum", "C", "THREE") + .editEnum(TestEnum.class, builder -> { + builder + .rename("A", "ONE") + .rename("B", "TWO") + .rename("C", "THREE") + .rename("FB", "FOUR") + .rename("Ea", "FIVE"); + }) .annotationAttribute("Ldata/types/rename/TestAnnotation;", "single", "value") .methodByInternal("data/types/rename/TestAnnotation", "single", "()Ldata/types/rename/TestEnum;", "value") .build(); diff --git a/src/test/java/io/papermc/asm/versioned/VersionedTester.java b/src/test/java/io/papermc/asm/versioned/VersionedTester.java new file mode 100644 index 0000000..f2ced83 --- /dev/null +++ b/src/test/java/io/papermc/asm/versioned/VersionedTester.java @@ -0,0 +1,46 @@ +package io.papermc.asm.versioned; + +import io.papermc.asm.ApiVersion; +import io.papermc.asm.rules.RewriteRule; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.function.Function; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +public class VersionedTester { + + private final VersionedRuleFactory factory; + private final List versions; + + public VersionedTester(final VersionedRuleFactory factory, final List versions) { + this.factory = factory; + this.versions = versions; + } + + @SuppressWarnings("unchecked") + public void test(final Function comparisonGetter, final Map expectedValues) { + final Map sortedExpectedValues = new TreeMap<>(expectedValues); + if (sortedExpectedValues.size() + 1 > this.versions.size()) { + throw new IllegalArgumentException("Expected values size does not match versions size"); + } + final Iterator> expectedEntryIter = sortedExpectedValues.entrySet().iterator(); + Map.Entry current = expectedEntryIter.next(); + for (int i = 0; i < this.versions.size() - 1; i++) { + final ApiVersion version = this.versions.get(i); + final R rule = (R) this.factory.createRule(version); + if (version.isNewerThan(current.getKey())) { + current = expectedEntryIter.next(); + } + final C expected = current.getValue(); + final C actual = comparisonGetter.apply(rule); + assertEquals(expected, actual, "Expected " + expected + " but got " + actual + " for version " + version); + } + assertFalse(expectedEntryIter.hasNext()); + final RewriteRule rule = this.factory.createRule(this.versions.get(this.versions.size() - 1)); + assertEquals(RewriteRule.EMPTY, rule, "Expected empty rule for version " + this.versions.get(this.versions.size() - 1)); + } +} diff --git a/src/testData/java/data/rename/RenameTest.java b/src/testData/java/data/rename/RenameTest.java index 76ce74a..9533019 100644 --- a/src/testData/java/data/rename/RenameTest.java +++ b/src/testData/java/data/rename/RenameTest.java @@ -15,6 +15,13 @@ public static void entry() throws ReflectiveOperationException { checkAnnotation(RenameTest.class.getDeclaredMethod("entry")); checkAnnotation(RenameTest.class.getDeclaredField("field")); checkAnnotation(RenameTest.class.getDeclaredField("otherField")); + + final TestEnum a = TestEnum.valueOf("A"); + System.out.println(a); + final TestEnum fb = TestEnum.valueOf("FB"); + System.out.println(fb); + final TestEnum ea = TestEnum.valueOf("Ea"); + System.out.println(ea); } private static void checkAnnotation(final AnnotatedElement element) { diff --git a/src/testData/java/data/types/rename/TestEnum.java b/src/testData/java/data/types/rename/TestEnum.java index ad396a8..0a46d4f 100644 --- a/src/testData/java/data/types/rename/TestEnum.java +++ b/src/testData/java/data/types/rename/TestEnum.java @@ -4,5 +4,7 @@ public enum TestEnum { A, B, C, + FB, // FB and Ea have the same string hashCode + Ea, ; } diff --git a/src/testData/resources/expected/data/rename/RenameTest.class b/src/testData/resources/expected/data/rename/RenameTest.class index 4228c7ef263d2850cbc22a345c59d78c9263e54e..d55c38b7b6d973051e3a4601d774b9b5e8e03459 100644 GIT binary patch delta 1438 zcma)6OH&+G6#j1a4Ba%%BaKW5@){syXdnXtBm%|^WI&Q3#6UtBqS!$52*WU81|$(3 z--&NcuiRNySyQDOV^t6=%W~~6aG|bT`6G-;N1`~~F5~%X(165%GZPQ6^xZ?`~9g)N}XVZx$oz!A< zBD=nvTI_G}>H^hXqjIX)KpWaM95)d~hxdtcGNj{#fHFL&qnkGFXm~`Rp4*1JkA+!s z5|3)=GtrL$fd=pAK!f+A7%_^_gHt9R$7ydxv3)pbVhCrvUzBLkFh(>yVPX_#y;}9G z-iCy1IOe6*D!m8eCNAJf?~WQ5m%Ja;1#d7gRV^I0UznwG=^62@IX9hMUQ1;(xL!W+ zQ=&WVq}}ycW_83(x$92Woo||Vvd&EQM%vBHth*^^)t#AiGue1*W3}n8dGsDQk8{Ds zQWMv)Za_mu;Ao`9W30?X5*w1-ra*I~Wm*PFWC&X_ctfD<-^11Lynw#sWR`~3=3N0b z(lTn|1-xir8##dj_xgsDWQ&T37gNhSub6llugJw$bv&ixijI_yH63Yz!0_nhxQ@4^ zH-71&j&}sYULqLIX?RcGIFI{2J`gCL$U1W?V@`U~nN7OrBngFYMZpn-aFKSxN0Ycr zYe8U&F($4eg$r{uM^OEryzoEshNhwV6;xNTCfzgw%n71^dYU^&X$5W2{tDu*-|{N8 zTm}q0O>OQc;2F#i6{3Wzs$YDMR%LA5`?TO#&Z^l(_S}~^YVV-c>Lf@Iv`WxLFh51|_s=;I_}IE)cg;yn4v!_>23I~wpTxqulJaOer< zt_awuE0@-n(@t7i|r3cf>+&t(tK z36O*2wTGJmyNK^Va&Fzr;k>{0fM1Brz*{oOtP-{4R);P!kUT>B4X}VkA`=lT@t6Y3 zL_xuDSy`cc+zjzVqJj+MiFRydfa87XbNXwP03}9g0dg3m(@Pnm!~y*q+6mjL*}-cc z;j}uCN55^o?xPa{qITPQ!$(a)qB`5^@{wLZbjY?k_mM`VKKvQ?>Z8byY#OFCQ(7q< zlpZU0E00R+b{^%{n|YL2Z|6~Hy_-kC-o=MsFh$@F^%#opvqI6Z&6h_#c7X6NZ2Qas Za-SRQS4zznQWCF#DW-Q0UzG(3hJb4l zBU^i1=Z2tdbjqnt*z#$KP&UiyzUI1AisW`I6A_+A7dyy^q3!gE=$q1Yg)?hS^hz}& z6MmccmpE%IAQTOb-MA(>d6tp>Y`(>Qhfm}$zom4zMYEyUpwb4*icL;zaHb8<*`q|D yvqj)885`5hZV}flzcOd-#_a{%=8o_q%FKyO#k?R-NjoEiyLQyQtv(9>Gx7^BzGAfi diff --git a/src/testDataNewTargets/java/data/types/rename/RenamedTestEnum.java b/src/testDataNewTargets/java/data/types/rename/RenamedTestEnum.java index 34f0b33..ac42c78 100644 --- a/src/testDataNewTargets/java/data/types/rename/RenamedTestEnum.java +++ b/src/testDataNewTargets/java/data/types/rename/RenamedTestEnum.java @@ -4,5 +4,7 @@ public enum RenamedTestEnum { ONE, TWO, THREE, + FOUR, + FIVE, ; } From 5569ad738cc090fd2cffb654715993426b90c423 Mon Sep 17 00:00:00 2001 From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> Date: Fri, 27 Sep 2024 11:29:27 -0700 Subject: [PATCH 3/4] Manually emit stack frames in EnumValueOfRewriteRule (#33) This is emitting uncompressed/expanded frames which will need to be computed/compressed on write, but should still be better than recomputing frames for everything. --- .../papermc/asm/rules/rename/EnumValueOfRewriteRule.java | 7 +++++++ src/test/java/io/papermc/asm/TestUtil.java | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/papermc/asm/rules/rename/EnumValueOfRewriteRule.java b/src/main/java/io/papermc/asm/rules/rename/EnumValueOfRewriteRule.java index c47da46..139233b 100644 --- a/src/main/java/io/papermc/asm/rules/rename/EnumValueOfRewriteRule.java +++ b/src/main/java/io/papermc/asm/rules/rename/EnumValueOfRewriteRule.java @@ -66,6 +66,8 @@ public void generateMethod(final GeneratorAdapterFactory factory, final MethodCa methodGenerator.visitLookupSwitchInsn(lookupSwitchEndLabel, lookupSwitchKeys, labels); for (int i = 0; i < labels.length; i++) { methodGenerator.mark(labels[i]); + // LocalVariableSorter will insert the trailing int local for this and all following visitFrame calls; adding it manually would cause duplicate locals in the frame + methodGenerator.visitFrame(Opcodes.F_NEW, 1, new Object[]{"java/lang/String"}, 1, new Object[]{"java/lang/String"}); // generate case final List matchingStrings = hashToField.get(lookupSwitchKeys[i]); if (matchingStrings.size() == 1) { @@ -93,11 +95,13 @@ public void generateMethod(final GeneratorAdapterFactory factory, final MethodCa methodGenerator.goTo(lookupSwitchEndLabel); if (nestedLabels[j] != lookupSwitchEndLabel) { methodGenerator.mark(nestedLabels[j]); // mark start of next label (except last one) + methodGenerator.visitFrame(Opcodes.F_NEW, 1, new Object[]{"java/lang/String"}, 1, new Object[]{"java/lang/String"}); } } } } methodGenerator.mark(lookupSwitchEndLabel); + methodGenerator.visitFrame(Opcodes.F_NEW, 1, new Object[]{"java/lang/String"}, 1, new Object[]{"java/lang/String"}); methodGenerator.loadLocal(tableSwitchIndexLocal); final Label[] tableSwitchLabels = new Label[tableSwitchIndexToRenamedField.length]; for (int i = 0; i < tableSwitchLabels.length; i++) { @@ -108,12 +112,15 @@ public void generateMethod(final GeneratorAdapterFactory factory, final MethodCa methodGenerator.visitTableSwitchInsn(0, tableSwitchIndexToRenamedField.length - 1, tableSwitchDefaultLabel, tableSwitchLabels); for (int i = 0; i < tableSwitchIndexToRenamedField.length; i++) { methodGenerator.mark(tableSwitchLabels[i]); + methodGenerator.visitFrame(Opcodes.F_NEW, 1, new Object[]{"java/lang/String"}, 1, new Object[]{"java/lang/String"}); methodGenerator.push(tableSwitchIndexToRenamedField[i]); methodGenerator.goTo(tableSwitchEndLabel); } methodGenerator.mark(tableSwitchDefaultLabel); + methodGenerator.visitFrame(Opcodes.F_NEW, 1, new Object[]{"java/lang/String"}, 1, new Object[]{"java/lang/String"}); methodGenerator.loadArg(0); // default to the passed in value methodGenerator.mark(tableSwitchEndLabel); + methodGenerator.visitFrame(Opcodes.F_NEW, 1, new Object[]{"java/lang/String"}, 2, new Object[]{"java/lang/String", "java/lang/String"}); methodGenerator.invokeStatic(Type.getType(original.owner().descriptorString()), new Method(original.name(), original.descriptor().descriptorString())); methodGenerator.returnValue(); methodGenerator.endMethod(); diff --git a/src/test/java/io/papermc/asm/TestUtil.java b/src/test/java/io/papermc/asm/TestUtil.java index 5d9a926..88fa2c5 100644 --- a/src/test/java/io/papermc/asm/TestUtil.java +++ b/src/test/java/io/papermc/asm/TestUtil.java @@ -54,9 +54,9 @@ public byte[] process(final byte[] bytes) { final ClassReader classReader = new ClassReader(bytes); final ClassWriter classWriter; if (this.copyFromClassReader()) { - classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); + classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS); } else { - classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); + classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); } classReader.accept(this.factory.createVisitor(classWriter), 0); return classWriter.toByteArray(); From 51a731b60f81672071f8fb7ac1e69adbef21287c Mon Sep 17 00:00:00 2001 From: Jake Potrebic Date: Fri, 27 Sep 2024 17:55:21 -0700 Subject: [PATCH 4/4] more work on renaming --- build.gradle.kts | 2 +- .../asm/rules/rename/EnumRenameBuilder.java | 18 ++-- .../papermc/asm/rules/rename/EnumRenamer.java | 20 +++- .../rules/rename/EnumValueOfRewriteRule.java | 4 +- .../papermc/asm/rules/rename/RenameRule.java | 65 +++++++++--- .../asm/rules/rename/RenameRuleBuilder.java | 98 +++++++------------ .../rules/rename/RenameRuleBuilderImpl.java | 64 ++++++++++++ .../asm/rules/rename/asm/package-info.java | 5 + ... => OwnedVersionedRuleFactoryFactory.java} | 8 +- ...OwnedVersionedRuleFactoryFactoryImpl.java} | 9 +- .../asm/versioned/VersionedRuleFactory.java | 12 +-- .../matcher/VersionedMatcherBase.java | 6 +- .../asm/rules/rename/RenameRuleTest.java | 64 ++++++++++-- 13 files changed, 267 insertions(+), 108 deletions(-) create mode 100644 src/main/java/io/papermc/asm/rules/rename/RenameRuleBuilderImpl.java create mode 100644 src/main/java/io/papermc/asm/rules/rename/asm/package-info.java rename src/main/java/io/papermc/asm/versioned/{VersionedRuleFactoryBuilder.java => OwnedVersionedRuleFactoryFactory.java} (94%) rename src/main/java/io/papermc/asm/versioned/{VersionedRuleFactoryBuilderImpl.java => OwnedVersionedRuleFactoryFactoryImpl.java} (85%) diff --git a/build.gradle.kts b/build.gradle.kts index 1c4bdc3..f13b24e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -89,7 +89,7 @@ val filtered = tasks.register("filteredTestClasspath") { dependencies { implementation(mainForNewTargets.output) - testImplementation(files(filtered.flatMap { it.outputDir })) + testRuntimeOnly(files(filtered.flatMap { it.outputDir })) // only have access to old targets at runtime, don't use them in actual tests testImplementation(testDataNewTargets.output) testDataNewTargets.implementationConfigurationName(mainForNewTargets.output) diff --git a/src/main/java/io/papermc/asm/rules/rename/EnumRenameBuilder.java b/src/main/java/io/papermc/asm/rules/rename/EnumRenameBuilder.java index 625f37a..2de4403 100644 --- a/src/main/java/io/papermc/asm/rules/rename/EnumRenameBuilder.java +++ b/src/main/java/io/papermc/asm/rules/rename/EnumRenameBuilder.java @@ -10,22 +10,22 @@ public final class EnumRenameBuilder { private final ClassDesc enumTypeDesc; - private @Nullable ClassDesc optionalEnumReplacementImpl; + private @Nullable ClassDesc alternateValueOfOwner; private final Map enumFieldRenames = new HashMap<>(); EnumRenameBuilder(final ClassDesc enumTypeDesc) { this.enumTypeDesc = enumTypeDesc; } - public EnumRenameBuilder enumReplacementImpl(final Class type) { - return this.enumReplacementImpl(desc(type)); + public EnumRenameBuilder alternateValueOfOwner(final Class type) { + return this.alternateValueOfOwner(desc(type)); } - public EnumRenameBuilder enumReplacementImpl(final ClassDesc type) { + public EnumRenameBuilder alternateValueOfOwner(final ClassDesc type) { if (this.enumTypeDesc.equals(type)) { throw new IllegalArgumentException("Cannot replace an enum with itself"); } - this.optionalEnumReplacementImpl = type; + this.alternateValueOfOwner = type; return this; } @@ -34,11 +34,7 @@ public EnumRenameBuilder rename(final String legacyName, final String newName) { return this; } - void apply(final RenameRuleBuilder renameRuleBuilder) { - this.enumFieldRenames.forEach((legacyName, newName) -> { - renameRuleBuilder.fieldByDesc(this.enumTypeDesc, legacyName, newName); - }); - final Map copy = Map.copyOf(this.enumFieldRenames); - renameRuleBuilder.enumValueOfFieldRenames.put(this.enumTypeDesc, new EnumRenamer(this.enumTypeDesc, this.optionalEnumReplacementImpl, copy)); + EnumRenamer build() { + return new EnumRenamer(this.enumTypeDesc, this.alternateValueOfOwner, Map.copyOf(this.enumFieldRenames)); } } diff --git a/src/main/java/io/papermc/asm/rules/rename/EnumRenamer.java b/src/main/java/io/papermc/asm/rules/rename/EnumRenamer.java index d744344..b77d915 100644 --- a/src/main/java/io/papermc/asm/rules/rename/EnumRenamer.java +++ b/src/main/java/io/papermc/asm/rules/rename/EnumRenamer.java @@ -1,8 +1,26 @@ package io.papermc.asm.rules.rename; import java.lang.constant.ClassDesc; +import java.util.HashMap; import java.util.Map; +import java.util.Objects; import org.checkerframework.checker.nullness.qual.Nullable; -public record EnumRenamer(ClassDesc typeDesc, @Nullable ClassDesc optionalReplacementImpl, Map fieldRenames) { +public record EnumRenamer(ClassDesc typeDesc, @Nullable ClassDesc alternateValueOfOwner, Map fieldRenames) { + + public EnumRenamer { + fieldRenames = Map.copyOf(fieldRenames); + } + + EnumRenamer overwrite(final EnumRenamer other) { + if (!this.typeDesc.equals(other.typeDesc)) { + throw new IllegalArgumentException("Cannot merge EnumRenamers with different typeDesc"); + } else if (!Objects.equals(this.alternateValueOfOwner, other.alternateValueOfOwner)) { + throw new IllegalArgumentException("Cannot merge EnumRenamers with different alternateValueOfOwner"); + } + final Map newFieldRenames = new HashMap<>(); + newFieldRenames.putAll(this.fieldRenames); + newFieldRenames.putAll(other.fieldRenames); + return new EnumRenamer(this.typeDesc, this.alternateValueOfOwner, newFieldRenames); + } } diff --git a/src/main/java/io/papermc/asm/rules/rename/EnumValueOfRewriteRule.java b/src/main/java/io/papermc/asm/rules/rename/EnumValueOfRewriteRule.java index 139233b..4c2bd21 100644 --- a/src/main/java/io/papermc/asm/rules/rename/EnumValueOfRewriteRule.java +++ b/src/main/java/io/papermc/asm/rules/rename/EnumValueOfRewriteRule.java @@ -28,8 +28,8 @@ final class EnumValueOfRewriteRule implements GeneratedStaticRewrite { EnumValueOfRewriteRule(final EnumRenamer renamer) { this.owners.add(renamer.typeDesc()); - if (renamer.optionalReplacementImpl() != null) { - this.owners.add(renamer.optionalReplacementImpl()); + if (renamer.alternateValueOfOwner() != null) { + this.owners.add(renamer.alternateValueOfOwner()); } this.matcher = MethodMatcher.builder() .match("valueOf", b -> b.statik().desc(MethodTypeDesc.of(renamer.typeDesc(), ConstantDescs.CD_String))) diff --git a/src/main/java/io/papermc/asm/rules/rename/RenameRule.java b/src/main/java/io/papermc/asm/rules/rename/RenameRule.java index 68b80ef..cb3a104 100644 --- a/src/main/java/io/papermc/asm/rules/rename/RenameRule.java +++ b/src/main/java/io/papermc/asm/rules/rename/RenameRule.java @@ -2,35 +2,78 @@ import io.papermc.asm.rules.RewriteRule; import io.papermc.asm.rules.rename.asm.FixedClassRemapper; +import io.papermc.asm.versioned.ApiVersion; +import io.papermc.asm.versioned.VersionedRuleFactory; import java.lang.constant.ClassDesc; import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.NavigableMap; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.objectweb.asm.commons.Remapper; import org.objectweb.asm.commons.SimpleRemapper; public final class RenameRule implements RewriteRule.Delegate { - private final RewriteRule rule; + public static RenameRuleBuilder builder() { + return new RenameRuleBuilderImpl(); + } - public RenameRule(final Map renames, final Map enumValueOfFieldRenames) { - final Remapper remapper = new SimpleRemapper(Map.copyOf(renames)); + private final Map renames; + private final Map enumFieldRenames; + private @MonotonicNonNull RewriteRule rule; - final List rules = new ArrayList<>(enumValueOfFieldRenames.size() + 1); - enumValueOfFieldRenames.forEach((classDesc, enumRenamer) -> { - rules.add(new EnumValueOfRewriteRule(enumRenamer)); - }); - rules.add((api, parent, context) -> new FixedClassRemapper(api, parent, remapper)); + public RenameRule(final Map renames, final Map enumFieldRenames) { + this.renames = Map.copyOf(renames); + this.enumFieldRenames = Map.copyOf(enumFieldRenames); + } - this.rule = RewriteRule.chain(rules); + public Map renames() { + return this.renames; } - public static RenameRuleBuilder builder() { - return new RenameRuleBuilder(); + public Map enumFieldRenames() { + return this.enumFieldRenames; } @Override public RewriteRule delegate() { + if (this.rule == null) { + final Remapper remapper = new SimpleRemapper(Map.copyOf(this.renames)); + + final List rules = new ArrayList<>(this.enumFieldRenames.size() + 1); + this.enumFieldRenames.forEach((classDesc, enumRenamer) -> { + rules.add(new EnumValueOfRewriteRule(enumRenamer)); + }); + rules.add((api, parent, context) -> new FixedClassRemapper(api, parent, remapper)); + + this.rule = RewriteRule.chain(rules); + } return this.rule; } + + public record Versioned(NavigableMap versions) implements VersionedRuleFactory { + + @Override + public RewriteRule createRule(final ApiVersion apiVersion) { + final List toMerge = new ArrayList<>(this.versions.tailMap(apiVersion, true).values()); + if (toMerge.isEmpty()) { + return RewriteRule.EMPTY; + } else if (toMerge.size() == 1) { + return toMerge.get(0); + } + Collections.reverse(toMerge); + final Map regularRenames = new HashMap<>(); + final Map enumFieldRenames = new HashMap<>(); + for (final RenameRule renameRule : toMerge) { + regularRenames.putAll(renameRule.renames); + renameRule.enumFieldRenames.forEach((classDesc, renamer) -> { + enumFieldRenames.merge(classDesc, renamer, EnumRenamer::overwrite); + }); + } + return new RenameRule(regularRenames, enumFieldRenames); + } + } } diff --git a/src/main/java/io/papermc/asm/rules/rename/RenameRuleBuilder.java b/src/main/java/io/papermc/asm/rules/rename/RenameRuleBuilder.java index c0c9d65..3db6c97 100644 --- a/src/main/java/io/papermc/asm/rules/rename/RenameRuleBuilder.java +++ b/src/main/java/io/papermc/asm/rules/rename/RenameRuleBuilder.java @@ -1,116 +1,86 @@ package io.papermc.asm.rules.rename; +import io.papermc.asm.util.Builder; import java.lang.constant.ClassDesc; import java.lang.constant.MethodTypeDesc; -import java.util.HashMap; -import java.util.Map; +import java.util.Set; import java.util.function.Consumer; import static io.papermc.asm.util.DescriptorUtils.desc; -import static io.papermc.asm.util.DescriptorUtils.toOwner; -public final class RenameRuleBuilder implements io.papermc.asm.util.Builder { +public interface RenameRuleBuilder extends Builder { - RenameRuleBuilder() { - } - - final Map mappings = new HashMap<>(); - final Map enumValueOfFieldRenames = new HashMap<>(); - - public RenameRuleBuilder methodByDesc(final Iterable owners, final String legacyMethodName, final MethodTypeDesc desc, final String newMethodName) { - for (final ClassDesc owner : owners) { - this.methodByDesc(owner, legacyMethodName, desc, newMethodName); + // + default RenameRuleBuilder methodByClass(final Set> owners, final String legacyMethodName, final MethodTypeDesc methodDesc, final String newMethodName) { + for (final Class owner : owners) { + this.methodByClass(owner, legacyMethodName, methodDesc, newMethodName); } return this; } - public RenameRuleBuilder methodByDesc(final ClassDesc owner, final String legacyMethodName, final MethodTypeDesc desc, final String newMethodName) { - return this.methodByInternal(toOwner(owner), legacyMethodName, desc.descriptorString(), newMethodName); + default RenameRuleBuilder methodByClass(final Class owner, final String legacyMethodName, final MethodTypeDesc methodDesc, final String newMethodName) { + return this.method(desc(owner), legacyMethodName, methodDesc, newMethodName); } - public RenameRuleBuilder methodByInternal(final Iterable owners, final String legacyMethodName, final MethodTypeDesc desc, final String newMethodName) { - for (final String owner : owners) { - this.methodByInternal(owner, legacyMethodName, desc.descriptorString(), newMethodName); + default RenameRuleBuilder method(final Set owners, final String legacyMethodName, final MethodTypeDesc methodDesc, final String newMethodName) { + for (final ClassDesc owner : owners) { + this.method(owner, legacyMethodName, methodDesc, newMethodName); } return this; } - public RenameRuleBuilder methodByInternal(final String owner, final String legacyMethodName, final String desc, final String newMethodName) { - this.mappings.put("%s.%s%s".formatted(owner, legacyMethodName, desc), newMethodName); - return this; - } + RenameRuleBuilder method(ClassDesc owner, String legacyMethodName, MethodTypeDesc methodDesc, final String newMethodName); + // - public RenameRuleBuilder fieldByDesc(final Iterable owners, final String legacyFieldName, final String newFieldName) { - for (final ClassDesc owner : owners) { - this.fieldByDesc(owner, legacyFieldName, newFieldName); + // + default RenameRuleBuilder fieldByClass(final Set> owners, final String legacyFieldName, final String newFieldName) { + for (final Class owner : owners) { + this.fieldByClass(owner, legacyFieldName, newFieldName); } return this; } - public RenameRuleBuilder fieldByDesc(final ClassDesc owner, final String legacyFieldName, final String newFieldName) { - return this.fieldByInternal(toOwner(owner), legacyFieldName, newFieldName); + default RenameRuleBuilder fieldByClass(final Class owner, final String legacyFieldName, final String newFieldName) { + return this.field(desc(owner), legacyFieldName, newFieldName); } - public RenameRuleBuilder fieldByInternal(final Iterable owners, final String legacyFieldName, final String newFieldName) { - for (final String owner : owners) { - this.fieldByInternal(owner, legacyFieldName, newFieldName); + default RenameRuleBuilder field(final Set owners, final String legacyFieldName, final String newFieldName) { + for (final ClassDesc owner : owners) { + this.field(owner, legacyFieldName, newFieldName); } return this; } - public RenameRuleBuilder fieldByInternal(final String owner, final String legacyFieldName, final String newFieldName) { - this.mappings.put("%s.%s".formatted(owner, legacyFieldName), newFieldName); - return this; - } + RenameRuleBuilder field(ClassDesc owner, String legacyFieldName, String newFieldName); + // /** * Note that you also have to remap the method for the annotation attribute. */ - public RenameRuleBuilder annotationAttribute(final ClassDesc owner, final String legacyName, final String newName) { - return this.annotationAttribute(owner.descriptorString(), legacyName, newName); + default RenameRuleBuilder annotationAttribute(final Class owner, final String legacyName, final String newName) { + return this.annotationAttribute(desc(owner), legacyName, newName); } /** * Note that you also have to remap the method for the annotation attribute. */ - public RenameRuleBuilder annotationAttribute(final String ownerDescriptor, final String legacyName, final String newName) { - if (!ownerDescriptor.startsWith("L") || !ownerDescriptor.endsWith(";")) { - throw new IllegalArgumentException("Invalid owner descriptor: %s".formatted(ownerDescriptor)); - } - // for some reason the remapper wants the Lpkg/name; format, but just for annotation attributes - this.mappings.put("%s.%s".formatted(ownerDescriptor, legacyName), newName); - return this; - } + RenameRuleBuilder annotationAttribute(ClassDesc owner, String legacyName, String newName); /** * Use {@code /} instead of {@code .}. */ - public RenameRuleBuilder type(final String legacyType, final ClassDesc newType) { - this.mappings.put(legacyType, toOwner(newType)); - return this; + default RenameRuleBuilder type(final String legacyType, final Class newType) { + return this.type(legacyType, desc(newType)); } /** * Use {@code /} instead of {@code .}. */ - public RenameRuleBuilder type(final String legacyType, final String newType) { - this.mappings.put(legacyType, newType); - return this; - } + RenameRuleBuilder type(String legacyType, ClassDesc newType); - public RenameRuleBuilder editEnum(final Class enumTypeDesc, final Consumer renamer) { - return this.editEnum(desc(enumTypeDesc), renamer); + default RenameRuleBuilder editEnum(final Class enumType, final Consumer renameBuilder) { + return this.editEnum(desc(enumType), renameBuilder); } - public RenameRuleBuilder editEnum(final ClassDesc enumTypeDesc, final Consumer renamer) { - final EnumRenameBuilder enumRenamer = new EnumRenameBuilder(enumTypeDesc); - renamer.accept(enumRenamer); - enumRenamer.apply(this); - return this; - } - - @Override - public RenameRule build() { - return new RenameRule(this.mappings, this.enumValueOfFieldRenames); - } + RenameRuleBuilder editEnum(ClassDesc enumTypeDesc, Consumer renameBuilder); } diff --git a/src/main/java/io/papermc/asm/rules/rename/RenameRuleBuilderImpl.java b/src/main/java/io/papermc/asm/rules/rename/RenameRuleBuilderImpl.java new file mode 100644 index 0000000..2cdafed --- /dev/null +++ b/src/main/java/io/papermc/asm/rules/rename/RenameRuleBuilderImpl.java @@ -0,0 +1,64 @@ +package io.papermc.asm.rules.rename; + +import java.lang.constant.ClassDesc; +import java.lang.constant.MethodTypeDesc; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; + +import static io.papermc.asm.util.DescriptorUtils.toOwner; + +final class RenameRuleBuilderImpl implements RenameRuleBuilder { + + RenameRuleBuilderImpl() { + } + + final Map mappings = new HashMap<>(); + final Map enumValueOfFieldRenames = new HashMap<>(); + + @Override + public RenameRuleBuilder method(final ClassDesc owner, final String legacyMethodName, final MethodTypeDesc methodDesc, final String newMethodName) { + this.mappings.put("%s.%s%s".formatted(toOwner(owner), legacyMethodName, methodDesc.descriptorString()), newMethodName); + return this; + } + + @Override + public RenameRuleBuilder field(final ClassDesc owner, final String legacyFieldName, final String newFieldName) { + this.mappings.put("%s.%s".formatted(toOwner(owner), legacyFieldName), newFieldName); + return this; + } + + @Override + public RenameRuleBuilder annotationAttribute(final ClassDesc owner, final String legacyName, final String newName) { + final String ownerDescriptor = owner.descriptorString(); + if (!ownerDescriptor.startsWith("L") || !ownerDescriptor.endsWith(";")) { + throw new IllegalArgumentException("Invalid owner descriptor: %s".formatted(ownerDescriptor)); + } + // for some reason the remapper wants the Lpkg/name; format, but just for annotation attributes + this.mappings.put("%s.%s".formatted(ownerDescriptor, legacyName), newName); + return this; + } + + @Override + public RenameRuleBuilder type(final String legacyType, final ClassDesc newType) { + this.mappings.put(legacyType, toOwner(newType)); + return this; + } + + @Override + public RenameRuleBuilder editEnum(final ClassDesc enumTypeDesc, final Consumer renamer) { + final EnumRenameBuilder enumRenamerBuilder = new EnumRenameBuilder(enumTypeDesc); + renamer.accept(enumRenamerBuilder); + final EnumRenamer enumRenamer = enumRenamerBuilder.build(); + enumRenamer.fieldRenames().forEach((legacyName, newName) -> { + this.field(enumTypeDesc, legacyName, newName); + }); + this.enumValueOfFieldRenames.put(enumTypeDesc, enumRenamer); + return this; + } + + @Override + public RenameRule build() { + return new RenameRule(this.mappings, this.enumValueOfFieldRenames); + } +} diff --git a/src/main/java/io/papermc/asm/rules/rename/asm/package-info.java b/src/main/java/io/papermc/asm/rules/rename/asm/package-info.java new file mode 100644 index 0000000..453d695 --- /dev/null +++ b/src/main/java/io/papermc/asm/rules/rename/asm/package-info.java @@ -0,0 +1,5 @@ +@DefaultQualifier(NonNull.class) +package io.papermc.asm.rules.rename.asm; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; diff --git a/src/main/java/io/papermc/asm/versioned/VersionedRuleFactoryBuilder.java b/src/main/java/io/papermc/asm/versioned/OwnedVersionedRuleFactoryFactory.java similarity index 94% rename from src/main/java/io/papermc/asm/versioned/VersionedRuleFactoryBuilder.java rename to src/main/java/io/papermc/asm/versioned/OwnedVersionedRuleFactoryFactory.java index d0c85ca..570aeb0 100644 --- a/src/main/java/io/papermc/asm/versioned/VersionedRuleFactoryBuilder.java +++ b/src/main/java/io/papermc/asm/versioned/OwnedVersionedRuleFactoryFactory.java @@ -10,10 +10,10 @@ import static io.papermc.asm.util.DescriptorUtils.desc; -public interface VersionedRuleFactoryBuilder { +public interface OwnedVersionedRuleFactoryFactory { - static VersionedRuleFactoryBuilder create(final Set owners) { - return new VersionedRuleFactoryBuilderImpl(owners); + static OwnedVersionedRuleFactoryFactory create(final Set owners) { + return new OwnedVersionedRuleFactoryFactoryImpl(owners); } // plain static rewrite @@ -82,5 +82,7 @@ default void changeReturnTypeToSub(final Class newReturnType, final Versioned void changeReturnTypeToSub(ClassDesc newReturnType, VersionedMethodMatcher versions); // + void addRuleFactory(VersionedRuleFactory factory); + VersionedRuleFactory build(); } diff --git a/src/main/java/io/papermc/asm/versioned/VersionedRuleFactoryBuilderImpl.java b/src/main/java/io/papermc/asm/versioned/OwnedVersionedRuleFactoryFactoryImpl.java similarity index 85% rename from src/main/java/io/papermc/asm/versioned/VersionedRuleFactoryBuilderImpl.java rename to src/main/java/io/papermc/asm/versioned/OwnedVersionedRuleFactoryFactoryImpl.java index 0e1460d..bba0029 100644 --- a/src/main/java/io/papermc/asm/versioned/VersionedRuleFactoryBuilderImpl.java +++ b/src/main/java/io/papermc/asm/versioned/OwnedVersionedRuleFactoryFactoryImpl.java @@ -11,12 +11,12 @@ import java.util.List; import java.util.Set; -public class VersionedRuleFactoryBuilderImpl implements VersionedRuleFactoryBuilder { +public class OwnedVersionedRuleFactoryFactoryImpl implements OwnedVersionedRuleFactoryFactory { final Set owners; private final List factories = new ArrayList<>(); - public VersionedRuleFactoryBuilderImpl(final Set owners) { + public OwnedVersionedRuleFactoryFactoryImpl(final Set owners) { this.owners = Set.copyOf(owners); } @@ -40,6 +40,11 @@ public void changeReturnTypeToSub(final ClassDesc newReturnType, final Versioned this.factories.add(new SubTypeReturnRewrite.Versioned(this.owners, newReturnType, versions)); } + @Override + public void addRuleFactory(final VersionedRuleFactory factory) { + this.factories.add(factory); + } + @Override public VersionedRuleFactory build() { return VersionedRuleFactory.chain(this.factories); diff --git a/src/main/java/io/papermc/asm/versioned/VersionedRuleFactory.java b/src/main/java/io/papermc/asm/versioned/VersionedRuleFactory.java index 9940868..c0889d3 100644 --- a/src/main/java/io/papermc/asm/versioned/VersionedRuleFactory.java +++ b/src/main/java/io/papermc/asm/versioned/VersionedRuleFactory.java @@ -18,25 +18,25 @@ public interface VersionedRuleFactory { VersionedRuleFactory EMPTY = apiVersion -> RewriteRule.EMPTY; @SafeVarargs - static VersionedRuleFactory forOwnerClass(final Class owner, final Consumer firstFactoryConsumer, final Consumer... factoryConsumers) { + static VersionedRuleFactory forOwnerClass(final Class owner, final Consumer firstFactoryConsumer, final Consumer... factoryConsumers) { return forOwnerClasses(Collections.singleton(owner), firstFactoryConsumer, factoryConsumers); } @SafeVarargs - static VersionedRuleFactory forOwnerClasses(final Set> owners, final Consumer firstFactoryConsumer, final Consumer... factoryConsumers) { + static VersionedRuleFactory forOwnerClasses(final Set> owners, final Consumer firstFactoryConsumer, final Consumer... factoryConsumers) { return forOwners(owners.stream().map(DescriptorUtils::desc).collect(Collectors.toUnmodifiableSet()), firstFactoryConsumer, factoryConsumers); } @SafeVarargs - static VersionedRuleFactory forOwner(final ClassDesc owner, final Consumer firstFactoryConsumer, final Consumer... factoryConsumers) { + static VersionedRuleFactory forOwner(final ClassDesc owner, final Consumer firstFactoryConsumer, final Consumer... factoryConsumers) { return forOwners(Collections.singleton(owner), firstFactoryConsumer, factoryConsumers); } @SafeVarargs - static VersionedRuleFactory forOwners(final Set owners, final Consumer firstFactoryConsumer, final Consumer... factoryConsumers) { - final VersionedRuleFactoryBuilder factory = VersionedRuleFactoryBuilder.create(owners); + static VersionedRuleFactory forOwners(final Set owners, final Consumer firstFactoryConsumer, final Consumer... factoryConsumers) { + final OwnedVersionedRuleFactoryFactory factory = OwnedVersionedRuleFactoryFactory.create(owners); firstFactoryConsumer.accept(factory); - for (final Consumer factoryConsumer : factoryConsumers) { + for (final Consumer factoryConsumer : factoryConsumers) { factoryConsumer.accept(factory); } return factory.build(); diff --git a/src/main/java/io/papermc/asm/versioned/matcher/VersionedMatcherBase.java b/src/main/java/io/papermc/asm/versioned/matcher/VersionedMatcherBase.java index f558dec..b5d8ad3 100644 --- a/src/main/java/io/papermc/asm/versioned/matcher/VersionedMatcherBase.java +++ b/src/main/java/io/papermc/asm/versioned/matcher/VersionedMatcherBase.java @@ -16,7 +16,11 @@ protected VersionedMatcherBase(final NavigableMap map) { } public RewriteRule ruleForVersion(final ApiVersion version, final Function creator) { - final Map.@Nullable Entry entry = this.map.ceilingEntry(version); + return ruleForVersion(this.map, version, creator); + } + + public static

RewriteRule ruleForVersion(final NavigableMap versions, final ApiVersion version, final Function creator) { + final Map.@Nullable Entry entry = versions.ceilingEntry(version); if (entry == null) { return RewriteRule.EMPTY; } diff --git a/src/test/java/io/papermc/asm/rules/rename/RenameRuleTest.java b/src/test/java/io/papermc/asm/rules/rename/RenameRuleTest.java index fbd4ac7..b3cdbc3 100644 --- a/src/test/java/io/papermc/asm/rules/rename/RenameRuleTest.java +++ b/src/test/java/io/papermc/asm/rules/rename/RenameRuleTest.java @@ -1,16 +1,33 @@ package io.papermc.asm.rules.rename; -import data.types.rename.TestEnum; +import data.types.rename.RenamedTestEnum; +import data.types.rename.TestAnnotation; +import io.papermc.asm.ApiVersion; import io.papermc.asm.TransformerTest; import io.papermc.asm.checks.TransformerCheck; +import io.papermc.asm.versioned.VersionedRuleFactory; +import java.lang.constant.ClassDesc; +import java.util.HashMap; +import java.util.Map; +import java.util.TreeMap; +import java.util.function.Function; +import org.jetbrains.annotations.Nullable; +import org.junit.jupiter.api.Test; + +import static io.papermc.asm.util.DescriptorUtils.fromOwner; +import static io.papermc.asm.util.DescriptorUtils.methodDesc; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; class RenameRuleTest { + private static final ClassDesc TEST_ENUM = fromOwner("data/types/rename/TestEnum"); + @TransformerTest("data.rename.RenameTest") - void testAnnotationSpecificRenames(final TransformerCheck check) { + void testRenamerRule(final TransformerCheck check) { final RenameRule rule = RenameRule.builder() - .type("data/types/rename/TestEnum", "data/types/rename/RenamedTestEnum") - .editEnum(TestEnum.class, builder -> { + .type("data/types/rename/TestEnum", RenamedTestEnum.class) + .editEnum(TEST_ENUM, builder -> { builder .rename("A", "ONE") .rename("B", "TWO") @@ -18,10 +35,45 @@ void testAnnotationSpecificRenames(final TransformerCheck check) { .rename("FB", "FOUR") .rename("Ea", "FIVE"); }) - .annotationAttribute("Ldata/types/rename/TestAnnotation;", "single", "value") - .methodByInternal("data/types/rename/TestAnnotation", "single", "()Ldata/types/rename/TestEnum;", "value") + .annotationAttribute(TestAnnotation.class, "single", "value") + .methodByClass(TestAnnotation.class, "single", methodDesc("()Ldata/types/rename/TestEnum;"), "value") .build(); check.run(rule); } + + @Test + void testVersionedRenamerRule() { + final Map versions = new HashMap<>(); + versions.put(ApiVersion.ONE, RenameRule.builder() + .methodByClass(TestAnnotation.class, "single", methodDesc("()Ldata/types/rename/TestEnum;"), "value") + .editEnum(TEST_ENUM, builder -> builder + .rename("A", "ONE") + ) + .build() + ); + versions.put(ApiVersion.THREE, RenameRule.builder() + .methodByClass(TestAnnotation.class, "newValue", methodDesc("()Ldata/types/rename/TestEnum;"), "value") + .annotationAttribute(TestAnnotation.class, "newValue", "value") + .editEnum(TEST_ENUM, builder -> builder + .rename("OTHER_A", "ONE") + .rename("B", "TWO") + ) + .build() + ); + + final VersionedRuleFactory factory = new RenameRule.Versioned(new TreeMap<>(versions)); + final RenameRule ruleOne = (RenameRule) factory.createRule(ApiVersion.ONE); + final RenameRule ruleTwo = (RenameRule) factory.createRule(ApiVersion.TWO); + assertEquals(method("single").apply(ruleOne), "value"); + assertEquals(method("newValue").apply(ruleOne), "value"); + + assertNull(method("single").apply(ruleTwo)); + assertEquals(method("newValue").apply(ruleTwo), "value"); + + } + + private static Function method(final String legacyName) { + return renameRule -> renameRule.renames().get("%s.%s%s".formatted("data/types/rename/TestAnnotation", legacyName, "()Ldata/types/rename/TestEnum;")); + } }