Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

initial work on supporting better versioning #32

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ val filtered = tasks.register<FilterTestClasspath>("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)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package io.papermc.asm.rules.method.params;

import io.papermc.asm.rules.RewriteRule;
import io.papermc.asm.rules.builder.matcher.method.targeted.TargetedMethodMatcher;
import io.papermc.asm.rules.method.OwnableMethodRewriteRule;
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.Set;
Expand All @@ -17,4 +21,12 @@
* @param staticHandler the method which will be used to convert the legacy type to the new type
*/
public record DirectParameterRewrite(Set<ClassDesc> owners, ClassDesc existingType, TargetedMethodMatcher methodMatcher, Method staticHandler) implements TargetedTypeGeneratedStaticRewrite.Parameter, OwnableMethodRewriteRule.Filtered {

public record Versioned(Set<ClassDesc> owners, ClassDesc existingType, VersionedTargetedMethodMatcher versions) implements VersionedRuleFactory {

@Override
public RewriteRule createRule(final ApiVersion apiVersion) {
return this.versions.ruleForVersion(apiVersion, pair -> new DirectParameterRewrite(this.owners, this.existingType, pair.matcher(), pair.staticHandler()));
}
}
}
Original file line number Diff line number Diff line change
@@ -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.targeted.TargetedMethodMatcher;
import io.papermc.asm.rules.method.OwnableMethodRewriteRule;
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;
Expand Down Expand Up @@ -54,4 +58,12 @@ public MethodRewrite<MethodCallData> createRewrite(final ClassProcessingContext
arguments[MethodRewrite.DYNAMIC_TYPE_IDX] = Type.getMethodType(newDynamicMethodType.descriptorString());
});
}

public record Versioned(Set<ClassDesc> 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()));
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<ClassDesc> 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()));
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -32,4 +36,12 @@ public record SubTypeReturnRewrite(Set<ClassDesc> owners, MethodMatcher methodMa
private MethodTypeDesc modifyMethodDescriptor(final MethodTypeDesc methodDescriptor) {
return methodDescriptor.changeReturnType(this.newReturnType());
}

public record Versioned(Set<ClassDesc> 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()));
}
}
}
40 changes: 40 additions & 0 deletions src/main/java/io/papermc/asm/rules/rename/EnumRenameBuilder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
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 alternateValueOfOwner;
private final Map<String, String> enumFieldRenames = new HashMap<>();

EnumRenameBuilder(final ClassDesc enumTypeDesc) {
this.enumTypeDesc = enumTypeDesc;
}

public EnumRenameBuilder alternateValueOfOwner(final Class<?> type) {
return this.alternateValueOfOwner(desc(type));
}

public EnumRenameBuilder alternateValueOfOwner(final ClassDesc type) {
if (this.enumTypeDesc.equals(type)) {
throw new IllegalArgumentException("Cannot replace an enum with itself");
}
this.alternateValueOfOwner = type;
return this;
}

public EnumRenameBuilder rename(final String legacyName, final String newName) {
this.enumFieldRenames.put(legacyName, newName);
return this;
}

EnumRenamer build() {
return new EnumRenamer(this.enumTypeDesc, this.alternateValueOfOwner, Map.copyOf(this.enumFieldRenames));
}
}
26 changes: 26 additions & 0 deletions src/main/java/io/papermc/asm/rules/rename/EnumRenamer.java
Original file line number Diff line number Diff line change
@@ -0,0 +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 alternateValueOfOwner, Map<String, String> 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<String, String> newFieldRenames = new HashMap<>();
newFieldRenames.putAll(this.fieldRenames);
newFieldRenames.putAll(other.fieldRenames);
return new EnumRenamer(this.typeDesc, this.alternateValueOfOwner, newFieldRenames);
}
}
144 changes: 144 additions & 0 deletions src/main/java/io/papermc/asm/rules/rename/EnumValueOfRewriteRule.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
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.OwnableMethodRewriteRule;
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, OwnableMethodRewriteRule.Filtered {

private final Set<ClassDesc> owners = new HashSet<>();
private final MethodMatcher matcher;
private final Map<String, String> fieldRenames;

EnumValueOfRewriteRule(final EnumRenamer renamer) {
this.owners.add(renamer.typeDesc());
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)))
.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<String, Integer> tableSwitchIndexMap = new LinkedHashMap<>();
final String[] tableSwitchIndexToRenamedField = new String[this.fieldRenames.size()];
final Map<Integer, List<String>> hashToField = new LinkedHashMap<>();
int curIdx = 0;
for (final Map.Entry<String, String> 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]);
// 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<String> 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.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++) {
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.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();
}

@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<ClassDesc> owners() {
return this.owners;
}
}
Loading